http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatusTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatusTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatusTest.java
new file mode 100644
index 0000000..9da855c
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletStatusToStatusTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.compute.functions;
+
+import static org.jclouds.compute.domain.NodeMetadata.Status.UNRECOGNIZED;
+import static org.testng.Assert.assertNotEquals;
+
+import org.jclouds.digitalocean2.domain.Droplet.Status;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "DropletStatusToStatusTest")
+public class DropletStatusToStatusTest {
+
+   @Test
+   public void testAllStatesHaveMapping() {
+      DropletStatusToStatus function = new DropletStatusToStatus();
+      for (Status status : Status.values()) {
+         assertNotEquals(function.apply(status), UNRECOGNIZED);
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
new file mode 100644
index 0000000..27dbad9
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/DropletToNodeMetadataTest.java
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.compute.functions;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.jclouds.compute.domain.Image.Status.AVAILABLE;
+import static org.jclouds.compute.domain.NodeMetadata.Status.RUNNING;
+import static org.jclouds.digitalocean2.domain.Droplet.Status.ACTIVE;
+import static org.testng.Assert.assertEquals;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Volume.Type;
+import org.jclouds.compute.domain.VolumeBuilder;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.Networks;
+import org.jclouds.digitalocean2.domain.Networks.Address;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.domain.LoginCredentials;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.inject.Guice;
+
+@Test(groups = "unit", testName = "DropletToNodeMetadataTest")
+public class DropletToNodeMetadataTest {
+
+   private org.jclouds.digitalocean2.domain.Image image;
+   
+   private Region region;
+   
+   private Set<Hardware> hardwares;
+
+   private Set<Image> images;
+
+   private Set<Location> locations;
+
+   private LoginCredentials credentials;
+
+   private DropletToNodeMetadata function;
+
+   @BeforeMethod
+   public void setup() {
+      image = org.jclouds.digitalocean2.domain.Image.create(1, "14.04 x64",
+            "distribution", "Ubuntu", "ubuntu-1404-x86", true, 
ImmutableList.of("sfo1"), new Date());
+      region = Region.create("sfo1", "San Francisco 1", 
ImmutableList.of("2gb"), true, ImmutableList.<String> of());
+      
+      images = ImmutableSet.of(new ImageBuilder()
+            .id("ubuntu-1404-x86")
+            .providerId("1")
+            .name("mock image")
+            .status(AVAILABLE)
+            .operatingSystem(
+                  OperatingSystem.builder().name("Ubuntu 14.04 
x86_64").description("Ubuntu").family(OsFamily.UBUNTU)
+                        
.version("10.04").arch("x86_64").is64Bit(true).build()).build());
+
+      hardwares = ImmutableSet.of(new 
HardwareBuilder().id("2gb").providerId("2gb").name("mock hardware")
+            .processor(new Processor(1.0, 1.0)).ram(2048)
+            .volume(new 
VolumeBuilder().size(20f).type(Type.LOCAL).build()).build());
+
+      locations = ImmutableSet.of(new LocationBuilder()
+            .id("sfo1")
+            .description("sfo1/San Francisco 1")
+            .scope(LocationScope.REGION)
+            .parent(
+                  new LocationBuilder().id("0").description("mock parent 
location").scope(LocationScope.PROVIDER)
+                        .build()).build());
+
+      credentials = 
LoginCredentials.builder().user("foo").password("bar").build();
+
+      function = createNodeParser(hardwares, images, locations, 
ImmutableMap.of("node#1", (Credentials) credentials));
+   }
+
+   @Test
+   public void testConvertDroplet() throws ParseException {
+      Droplet droplet = Droplet.create(
+            1,
+            "mock-droplet",
+            1,
+            1,
+            1,
+            false,
+            new Date(),
+            Droplet.Status.ACTIVE,
+            ImmutableList.<Integer> of(),
+            ImmutableList.<Integer> of(),
+            ImmutableList.<String> of(),
+            region,
+            image,
+            null,
+            "2gb",
+            Networks.create(
+                  ImmutableList.of(Address.create("84.45.69.3", 
"255.255.255.0", "84.45.69.1", "public"),
+                        Address.create("192.168.2.5", "255.255.255.0", 
"192.168.2.1", "private")),
+                  ImmutableList.<Networks.Address> of()), null);
+
+      NodeMetadata expected = new 
NodeMetadataBuilder().ids("1").hardware(getOnlyElement(hardwares))
+            
.imageId("ubuntu-1404-x86").status(RUNNING).location(getOnlyElement(locations)).name("mock-droplet")
+            .hostname("mock-droplet").group("mock").credentials(credentials)
+            
.publicAddresses(ImmutableSet.of("84.45.69.3")).privateAddresses(ImmutableSet.of("192.168.2.5"))
+            
.providerId("1").backendStatus(ACTIVE.name()).operatingSystem(getOnlyElement(images).getOperatingSystem())
+            .build();
+
+      NodeMetadata actual = function.apply(droplet);
+      assertNodeEquals(actual, expected);
+   }
+
+   @Test
+   public void testConvertDropletOldImage() throws ParseException {
+      // Use an image id that is not in the list of images
+      org.jclouds.digitalocean2.domain.Image image = 
org.jclouds.digitalocean2.domain.Image.create(2, "14.04 x64",
+            "distribution", "Ubuntu", "ubuntu2-1404-x86", true, 
ImmutableList.of("sfo1"), new Date());
+      
+      Droplet droplet = Droplet.create(
+            1,
+            "mock-droplet",
+            1,
+            1,
+            1,
+            false,
+            new Date(),
+            Droplet.Status.ACTIVE,
+            ImmutableList.<Integer> of(),
+            ImmutableList.<Integer> of(),
+            ImmutableList.<String> of(),
+            region,
+            image,
+            null,
+            "2gb",
+            Networks.create(
+                  ImmutableList.of(Address.create("84.45.69.3", 
"255.255.255.0", "84.45.69.1", "public"),
+                        Address.create("192.168.2.5", "255.255.255.0", 
"192.168.2.1", "private")),
+                  ImmutableList.<Networks.Address> of()), null);
+
+      NodeMetadata expected = new 
NodeMetadataBuilder().ids("1").hardware(getOnlyElement(hardwares)).imageId(null)
+            
.status(RUNNING).location(getOnlyElement(locations)).name("mock-droplet").hostname("mock-droplet")
+            
.group("mock").credentials(credentials).publicAddresses(ImmutableSet.of("84.45.69.3"))
+            
.privateAddresses(ImmutableSet.of("192.168.2.5")).providerId("1").backendStatus(ACTIVE.name())
+            .operatingSystem(null).build();
+
+      NodeMetadata actual = function.apply(droplet);
+      assertNodeEquals(actual, expected);
+   }
+
+   private static void assertNodeEquals(NodeMetadata actual, NodeMetadata 
expected) {
+      assertEquals(actual, expected);
+      // NodeMetadata equals method does not use all fields in equals. It 
assumes that same ids in same locations
+      // determine the equivalence
+      assertEquals(actual.getStatus(), expected.getStatus());
+      assertEquals(actual.getBackendStatus(), expected.getBackendStatus());
+      assertEquals(actual.getLoginPort(), expected.getLoginPort());
+      assertEquals(actual.getPublicAddresses(), expected.getPublicAddresses());
+      assertEquals(actual.getPrivateAddresses(), 
expected.getPrivateAddresses());
+      assertEquals(actual.getCredentials(), expected.getCredentials());
+      assertEquals(actual.getGroup(), expected.getGroup());
+      assertEquals(actual.getImageId(), expected.getImageId());
+      assertEquals(actual.getHardware(), expected.getHardware());
+      assertEquals(actual.getOperatingSystem(), expected.getOperatingSystem());
+      assertEquals(actual.getHostname(), expected.getHostname());
+   }
+
+   private DropletToNodeMetadata createNodeParser(final Set<Hardware> 
hardware, final Set<Image> images,
+         final Set<Location> locations, Map<String, Credentials> 
credentialStore) {
+      Supplier<Set<? extends Location>> locationSupplier = new Supplier<Set<? 
extends Location>>() {
+         @Override
+         public Set<? extends Location> get() {
+            return locations;
+         }
+      };
+
+      Supplier<Map<String, ? extends Hardware>> hardwareSupplier = new 
Supplier<Map<String, ? extends Hardware>>() {
+         @Override
+         public Map<String, ? extends Hardware> get() {
+            return Maps.uniqueIndex(hardware, new Function<Hardware, String>() 
{
+               @Override
+               public String apply(Hardware input) {
+                  return input.getId();
+               }
+            });
+         }
+      };
+
+      Supplier<Map<String, ? extends Image>> imageSupplier = new 
Supplier<Map<String, ? extends Image>>() {
+         @Override
+         public Map<String, ? extends Image> get() {
+            return Maps.uniqueIndex(images, new Function<Image, String>() {
+               @Override
+               public String apply(Image input) {
+                  return input.getId();
+               }
+            });
+         }
+      };
+
+      GroupNamingConvention.Factory namingConvention = 
Guice.createInjector().getInstance(GroupNamingConvention.Factory.class);
+
+      return new DropletToNodeMetadata(imageSupplier, hardwareSupplier, 
locationSupplier, new DropletStatusToStatus(),
+            namingConvention, credentialStore);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
new file mode 100644
index 0000000..6ab020c
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/ImageToImageTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.compute.functions;
+
+import static org.jclouds.compute.domain.Image.Status.AVAILABLE;
+import static org.testng.Assert.assertEquals;
+
+import java.util.Date;
+
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.digitalocean2.domain.Image;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+@Test(groups = "unit", testName = "ImageToImageTest")
+public class ImageToImageTest {
+
+   @Test
+   public void testConvertImage() {
+      Image image = Image.create(1, "14.04 x64", "distribution", "Ubuntu", 
"ubuntu-1404-x86", true,
+            ImmutableList.of("sfo1"), new Date());
+      org.jclouds.compute.domain.Image expected = new ImageBuilder()
+            .id("ubuntu-1404-x86")
+            .providerId("1")
+            .name("14.04 x64")
+            .description("Ubuntu 14.04 x64")
+            .status(AVAILABLE)
+            .operatingSystem(
+                  OperatingSystem.builder().name("Ubuntu").description("Ubuntu 
14.04 x64").family(OsFamily.UBUNTU)
+                        .version("14.04").arch("x64").is64Bit(true).build())
+            .userMetadata(ImmutableMap.of("publicImage", "true")).build();
+
+      org.jclouds.compute.domain.Image result = new 
ImageToImage().apply(image);
+      assertEquals(result, expected);
+      assertEquals(result.getDescription(), expected.getDescription());
+      assertEquals(result.getOperatingSystem(), expected.getOperatingSystem());
+      assertEquals(result.getStatus(), expected.getStatus());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
new file mode 100644
index 0000000..879091b
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/RegionToLocationTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.compute.functions;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+
+import org.jclouds.digitalocean2.DigitalOcean2ProviderMetadata;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.location.suppliers.all.JustProvider;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+@Test(groups = "unit", testName = "RegionToLocationTest")
+public class RegionToLocationTest {
+
+   @Test
+   public void testConvertRegion() {
+      DigitalOcean2ProviderMetadata metadata = new 
DigitalOcean2ProviderMetadata();
+      JustProvider locationsSupplier = new JustProvider(metadata.getId(), 
Suppliers.<URI> ofInstance(URI
+            .create(metadata.getEndpoint())), ImmutableSet.<String> of());
+
+      Region region = Region.create("reg1", "Region1", ImmutableList.<String> 
of(), true, ImmutableList.<String> of());
+      Location expected = new 
LocationBuilder().id("reg1").description("reg1/Region 1")
+            
.parent(getOnlyElement(locationsSupplier.get())).scope(LocationScope.REGION).build();
+
+      RegionToLocation function = new RegionToLocation(locationsSupplier);
+      assertEquals(function.apply(region), expected);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/SizeToHardwareTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/SizeToHardwareTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/SizeToHardwareTest.java
new file mode 100644
index 0000000..cba55bf
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/SizeToHardwareTest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.compute.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Volume.Type;
+import org.jclouds.compute.domain.VolumeBuilder;
+import org.jclouds.digitalocean2.domain.Size;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Unit tests for the {@link SizeToHardware} class.
+ */
+@Test(groups = "unit", testName = "SizeToHardwareTest")
+public class SizeToHardwareTest {
+
+   @Test
+   public void testConvertSize() {
+      Size size = Size.create("2gb", true, 1.0f, 10f, 0.05f, 2048, 1, 20, 
ImmutableList.<String> of());
+      Hardware expected = new 
HardwareBuilder().id("2gb").providerId("2gb").name("2gb")
+            .processor(new Processor(1.0, 1.0)).ram(2048)
+            .volume(new VolumeBuilder().size(20f).type(Type.LOCAL).build())
+            .userMetadata(ImmutableMap.of("costPerHour", "0.05", 
"costPerMonth", "10")).build();
+
+      SizeToHardware function = new SizeToHardware();
+      assertEquals(function.apply(size), expected);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKeyTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKeyTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKeyTest.java
new file mode 100644
index 0000000..c3a6cd2
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/functions/TemplateOptionsToStatementWithoutPublicKeyTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.compute.functions;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.scriptbuilder.domain.OsFamily;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.jclouds.scriptbuilder.domain.StatementList;
+import org.jclouds.scriptbuilder.statements.ssh.InstallRSAPrivateKey;
+import org.jclouds.ssh.SshKeys;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for the {@link TemplateOptionsToStatementWithoutPublicKey} class.
+ */
+@Test(groups = "unit", testName = 
"TemplateOptionsToStatementWithoutPublicKeyTest")
+public class TemplateOptionsToStatementWithoutPublicKeyTest {
+
+   @Test
+   public void 
testPublicKeyDoesNotGenerateAuthorizePublicKeyStatementIfOnlyPublicKeyOptionsConfigured()
 {
+      Map<String, String> keys = SshKeys.generate();
+      TemplateOptions options = 
TemplateOptions.Builder.authorizePublicKey(keys.get("public"));
+
+      TemplateOptionsToStatementWithoutPublicKey function = new 
TemplateOptionsToStatementWithoutPublicKey();
+      assertNull(function.apply(options));
+   }
+
+   @Test
+   public void 
testPublicAndRunScriptKeyDoesNotGenerateAuthorizePublicKeyStatementIfRunScriptPresent()
 {
+      Map<String, String> keys = SshKeys.generate();
+      TemplateOptions options = 
TemplateOptions.Builder.authorizePublicKey(keys.get("public")).runScript("uptime");
+
+      TemplateOptionsToStatementWithoutPublicKey function = new 
TemplateOptionsToStatementWithoutPublicKey();
+      Statement statement = function.apply(options);
+
+      assertEquals(statement.render(OsFamily.UNIX), "uptime\n");
+   }
+
+   @Test
+   public void 
testPublicAndPrivateKeyAndRunScriptDoesNotGenerateAuthorizePublicKeyStatementIfOtherOptionsPresent()
 {
+      Map<String, String> keys = SshKeys.generate();
+      TemplateOptions options = 
TemplateOptions.Builder.authorizePublicKey(keys.get("public"))
+            .installPrivateKey(keys.get("private")).runScript("uptime");
+
+      TemplateOptionsToStatementWithoutPublicKey function = new 
TemplateOptionsToStatementWithoutPublicKey();
+      Statement statement = function.apply(options);
+
+      assertTrue(statement instanceof StatementList);
+      StatementList statements = (StatementList) statement;
+
+      assertEquals(statements.size(), 2);
+      assertEquals(statements.get(0).render(OsFamily.UNIX), "uptime\n");
+      assertTrue(statements.get(1) instanceof InstallRSAPrivateKey);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptionsTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptionsTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptionsTest.java
new file mode 100644
index 0000000..982224c
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/compute/options/DigitalOcean2TemplateOptionsTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.compute.options;
+
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.compute.options.TemplateOptions;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+@Test(groups = "unit", testName = "DigitalOcean2TemplateOptionsTest")
+public class DigitalOcean2TemplateOptionsTest {
+
+   @Test
+   public void testSShKeyIds() {
+      TemplateOptions options = new 
DigitalOcean2TemplateOptions().sshKeyIds(ImmutableSet.of(1, 2, 3));
+      
assertEquals(options.as(DigitalOcean2TemplateOptions.class).getSshKeyIds(), 
ImmutableSet.of(1, 2, 3));
+   }
+
+   @Test
+   public void testPrivateNetworking() {
+      TemplateOptions options = new 
DigitalOcean2TemplateOptions().privateNetworking(true);
+      
assertEquals(options.as(DigitalOcean2TemplateOptions.class).getPrivateNetworking(),
 true);
+   }
+
+   @Test
+   public void testBackupsEnabled() {
+      TemplateOptions options = new 
DigitalOcean2TemplateOptions().backupsEnabled(true);
+      
assertEquals(options.as(DigitalOcean2TemplateOptions.class).getBackupsEnabled(),
 true);
+   }
+   
+   @Test
+   public void testAutoCreateKeyPair() {
+      TemplateOptions options = new 
DigitalOcean2TemplateOptions().autoCreateKeyPair(false);
+      
assertEquals(options.as(DigitalOcean2TemplateOptions.class).getAutoCreateKeyPair(),
 false);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/domain/OperatingSystemTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/domain/OperatingSystemTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/domain/OperatingSystemTest.java
new file mode 100644
index 0000000..d6bd0fc
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/domain/OperatingSystemTest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.domain;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "OperatingSystemTest")
+public class OperatingSystemTest {
+
+   public void testParseStandard64bit() {
+      OperatingSystem os = OperatingSystem.create("12.10 x64", "Ubuntu");
+
+      assertEquals(os.distribution(), Distribution.UBUNTU);
+      assertEquals(os.version(), "12.10");
+      assertEquals(os.arch(), "x64");
+      assertTrue(os.is64bit());
+   }
+
+   public void testLongVersionStandard64bit() {
+      OperatingSystem os = OperatingSystem.create("12.10.1 x64", "Ubuntu");
+
+      assertEquals(os.distribution(), Distribution.UBUNTU);
+      assertEquals(os.version(), "12.10.1");
+      assertEquals(os.arch(), "x64");
+      assertTrue(os.is64bit());
+   }
+
+   public void testParseStandard64bitWithPrefix() {
+      OperatingSystem os = OperatingSystem.create("Arch Linux 12.10 x64 
Desktop", "Arch Linux");
+
+      assertEquals(os.distribution(), Distribution.ARCHLINUX);
+      assertEquals(os.version(), "12.10");
+      assertEquals(os.arch(), "x64");
+      assertTrue(os.is64bit());
+   }
+
+   public void testParseStandard() {
+      OperatingSystem os = OperatingSystem.create("12.10 x32", "Ubuntu");
+
+      assertEquals(os.distribution(), Distribution.UBUNTU);
+      assertEquals(os.version(), "12.10");
+      assertEquals(os.arch(), "x32");
+      assertFalse(os.is64bit());
+
+      os = OperatingSystem.create("6.5 x64", "CentOS");
+
+      assertEquals(os.distribution(), Distribution.CENTOS);
+      assertEquals(os.version(), "6.5");
+      assertEquals(os.arch(), "x64");
+      assertTrue(os.is64bit());
+
+      os = OperatingSystem.create("6.5 x64", "Centos");
+
+      assertEquals(os.distribution(), Distribution.CENTOS);
+      assertEquals(os.version(), "6.5");
+      assertEquals(os.arch(), "x64");
+      assertTrue(os.is64bit());
+   }
+
+   public void testParseNoArch() {
+      OperatingSystem os = OperatingSystem.create("12.10", "Ubuntu");
+
+      assertEquals(os.distribution(), Distribution.UBUNTU);
+      assertEquals(os.version(), "12.10");
+      assertEquals(os.arch(), "");
+      assertFalse(os.is64bit());
+   }
+
+   public void testParseNoVersion() {
+      OperatingSystem os = OperatingSystem.create("x64", "Ubuntu");
+
+      assertEquals(os.distribution(), Distribution.UBUNTU);
+      assertEquals(os.version(), "");
+      assertEquals(os.arch(), "x64");
+      assertTrue(os.is64bit());
+   }
+
+   public void testParseUnknownDistribution() {
+      OperatingSystem os = OperatingSystem.create("12.04 x64", "Foo");
+
+      assertEquals(os.distribution(), Distribution.UNRECOGNIZED);
+      assertEquals(os.version(), "12.04");
+      assertEquals(os.arch(), "x64");
+      assertTrue(os.is64bit());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiLiveTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiLiveTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiLiveTest.java
new file mode 100644
index 0000000..44a9a17
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiLiveTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.features;
+
+import static 
org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.page;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
+@Test(groups = "live", testName = "ActionApiLiveTest")
+public class ActionApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+   public void testListActions() {
+      final AtomicInteger found = new AtomicInteger(0);
+      // DigitalOcean return 25 records per page by default. Inspect at most 2 
pages
+      assertTrue(api().list().concat().limit(50).allMatch(new 
Predicate<Action>() {
+         @Override
+         public boolean apply(Action input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.type());
+         }
+      }), "All actions must have the 'type' field populated");
+      assertTrue(found.get() > 0, "Expected some actions to be returned");
+   }
+   
+   public void testListActionsOnePage() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(page(1).perPage(5)).allMatch(new 
Predicate<Action>() {
+         @Override
+         public boolean apply(Action input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.type());
+         }
+      }), "All actions must have the 'type' field populated");
+      assertTrue(found.get() > 0, "Expected some actions to be returned");
+   }
+   
+   public void testGetAction() {
+      Optional<Action> first = api().list().concat().first();
+      assertTrue(first.isPresent(), "At least one action was expected to 
exist");
+      assertNotNull(api().get(first.get().id()));
+   }
+   
+   private ActionApi api() {
+      return api.actionApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiMockTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiMockTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiMockTest.java
new file mode 100644
index 0000000..aa890d5
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ActionApiMockTest.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static 
org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "ActionApiMockTest", singleThreaded = true)
+public class ActionApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+   public void testListActions() throws InterruptedException {
+      server.enqueue(jsonResponse("/actions-first.json"));
+      server.enqueue(jsonResponse("/actions-last.json"));
+
+      Iterable<Action> actions = api.actionApi().list().concat();
+
+      assertEquals(size(actions), 8); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/actions");
+      assertSent(server, "GET", "/actions?page=2&per_page=5");
+   }
+
+   public void testListActionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Action> actions = api.actionApi().list().concat();
+
+      assertTrue(isEmpty(actions));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/actions");
+   }
+
+   public void testListActionsWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/actions-first.json"));
+
+      Iterable<Action> actions = api.actionApi().list(page(1).perPage(5));
+
+      assertEquals(size(actions), 5);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/actions?page=1&per_page=5");
+   }
+
+   public void testListActionsWithOptionsReturns404() throws 
InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Action> actions = api.actionApi().list(page(1).perPage(5));
+
+      assertTrue(isEmpty(actions));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/actions?page=1&per_page=5");
+   }
+   
+   public void testGetAction() throws InterruptedException {
+      server.enqueue(jsonResponse("/action.json"));
+
+      Action action = api.actionApi().get(1);
+
+      assertEquals(action, actionFromResource("/action.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/actions/1");
+   }
+
+   public void testGetActionReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Action action = api.actionApi().get(1);
+
+      assertNull(action);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/actions/1");
+   }
+   
+   private Action actionFromResource(String resource) {
+      return onlyObjectFromResource(resource, new TypeToken<Map<String, 
Action>>() {
+         private static final long serialVersionUID = 1L;
+      }); 
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
new file mode 100644
index 0000000..f451d2e
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiLiveTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.features;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static java.util.logging.Logger.getAnonymousLogger;
+import static org.jclouds.digitalocean2.domain.Droplet.Status.ACTIVE;
+import static org.jclouds.digitalocean2.domain.Droplet.Status.OFF;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jclouds.compute.ComputeTestUtils;
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.Backup;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.DropletCreate;
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.domain.Kernel;
+import org.jclouds.digitalocean2.domain.Key;
+import org.jclouds.digitalocean2.domain.Region;
+import org.jclouds.digitalocean2.domain.Size;
+import org.jclouds.digitalocean2.domain.Snapshot;
+import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+
+@Test(groups = "live", testName = "DropletApiLiveTest")
+public class DropletApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+   private Region region;
+   private Size size;
+   private Image image;
+   private Key key;
+   private int dropletId = -1;
+   
+   @BeforeClass
+   public void setupDroplet() {
+      region = firstAvailableRegion();
+      size = cheapestSizeInRegion(region);
+      image = ubuntuImageInRegion(region);
+      
+      Map<String, String> keyPair = ComputeTestUtils.setupKeyPair();
+      key = api.keyApi().create(prefix + "-droplet-livetest", 
keyPair.get("public"));
+   }
+   
+   @AfterClass(alwaysRun = true)
+   public void tearDown() {
+      if (key != null) {
+         api.keyApi().delete(key.id());
+      }
+   }
+   
+   public void testCreate() {
+      DropletCreate dropletCreate = api().create(prefix + "-droplet-livetest", 
region.slug(), size.slug(), image.slug(),
+            
CreateDropletOptions.builder().backupsEnabled(true).addSshKeyId(key.id()).build());
+      
assertActionCompleted(getOnlyElement(dropletCreate.links().actions()).id());
+      dropletId = dropletCreate.droplet().id();
+      Droplet droplet = api().get(dropletId);
+      assertNotNull(droplet, "Droplet should not be null");
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testCreate")
+   public void testListDroplets() {
+      assertTrue(api().list().concat().anyMatch(new Predicate<Droplet>() {
+         @Override
+         public boolean apply(Droplet input) {
+            return input.id() == dropletId;
+         }
+      }), "The created droplet must be in the list");
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testListKernels() {
+      Iterable<Kernel> kernels = api().listKernels(dropletId).concat();
+      assertEquals(kernels.iterator().next().name(), 
"DO-recovery-static-fsck");
+   }
+   
+   @Test(dependsOnMethods = "testListKernels")
+   public void testPowerOff() {
+      Action action = api().powerOff(dropletId);
+      assertActionCompleted(action.id());
+      Droplet droplet = api().get(dropletId);
+      assertEquals(droplet.status(), OFF, "Droplet should be off");
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testPowerOff")
+   public void testSnapshots() {
+      Action action = api().snapshot(dropletId, prefix + dropletId + 
"-snapshot");
+      assertActionCompleted(action.id());
+      
+      List<Snapshot> snapshots = 
api().listSnapshots(dropletId).concat().toList();
+      assertEquals(snapshots.size(), 1, "Must contain 1 snapshot");
+      
+      for (Snapshot snapshot : snapshots) {
+         try {
+            api.imageApi().delete(snapshot.id());
+         } catch (Exception ex) {
+            getAnonymousLogger().warning("Could not delete snapshot: " + 
snapshot.id());
+         }
+      }
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testSnapshots")
+   public void testBackups() {
+      Iterable<Backup> backups = api().listBackups(dropletId).concat();
+      // Backups are automatically taken by DO on a weekly basis, so we can't 
guarantee
+      // there will be any backup available. Just check that the call succeeds
+      assertNotNull(backups);
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testSnapshots")
+   public void testListActions() {
+      FluentIterable<Action> actions = api().listActions(dropletId).concat();
+      assertTrue(actions.anyMatch(new Predicate<Action>() {
+         @Override
+         public boolean apply(Action input) {
+            return "snapshot".equals(input.type());
+         }
+      }));
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testSnapshots")
+   public void testPowerOn() {
+      // Apparently droplets are automatically powered on after the snapshot 
process
+      Action action = api().powerOff(dropletId);
+      assertActionCompleted(action.id());
+      
+      action = api().powerOn(dropletId);
+      assertActionCompleted(action.id());
+      Droplet droplet = api().get(dropletId);
+      assertEquals(droplet.status(), ACTIVE, "Droplet should be Active");
+   }
+   
+   @Test(groups = "live", dependsOnMethods = "testPowerOn")
+   public void testReboot() {
+      Action action = api().reboot(dropletId);
+      assertActionCompleted(action.id());
+      Droplet droplet = api().get(dropletId);
+      assertEquals(droplet.status(), ACTIVE, "Droplet should be off");
+   }
+   
+   @Test(groups = "live", dependsOnMethods = "testReboot")
+   public void testPowerCycle() {
+      Action action = api().powerCycle(dropletId);
+      assertActionCompleted(action.id());
+      Droplet droplet = api().get(dropletId);
+      assertEquals(droplet.status(), ACTIVE, "Droplet should be off");
+   }
+   
+   @Test(groups = "live", dependsOnMethods = "testPowerCycle")
+   public void testShutdown() {
+      Action action = api().shutdown(dropletId);
+      assertActionCompleted(action.id());
+      // The shutdown action can fail if the shutdown command fails in the 
guest OS
+      // We can not guarantee that a graceful shutdown action will en up in 
the droplet
+      // being in OFF state
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testShutdown", alwaysRun = true)
+   public void testDelete() throws InterruptedException {
+      if (dropletId != -1) {
+         api().delete(dropletId);
+         assertNodeTerminated(dropletId);
+         assertNull(api().get(dropletId));
+      }
+   }
+   
+   private DropletApi api() {
+      return api.dropletApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiMockTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiMockTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiMockTest.java
new file mode 100644
index 0000000..dcd6352
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/DropletApiMockTest.java
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static 
org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.digitalocean2.domain.Action;
+import org.jclouds.digitalocean2.domain.Backup;
+import org.jclouds.digitalocean2.domain.Droplet;
+import org.jclouds.digitalocean2.domain.DropletCreate;
+import org.jclouds.digitalocean2.domain.Kernel;
+import org.jclouds.digitalocean2.domain.Snapshot;
+import org.jclouds.digitalocean2.domain.options.CreateDropletOptions;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "DropletApiMockTest", singleThreaded = true)
+public class DropletApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+   public void testListDroplets() throws InterruptedException {
+      server.enqueue(jsonResponse("/droplets-first.json"));
+      server.enqueue(jsonResponse("/droplets-last.json"));
+
+      Iterable<Droplet> droplets = api.dropletApi().list().concat();
+
+      assertEquals(size(droplets), 2); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/droplets");
+      assertSent(server, "GET", "/droplets?page=2&per_page=1");
+   }
+
+   public void testListDropletsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Droplet> droplets = api.dropletApi().list().concat();
+
+      assertTrue(isEmpty(droplets));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/droplets");
+   }
+
+   public void testListDropletsWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/droplets-first.json"));
+
+      Iterable<Droplet> droplets = api.dropletApi().list(page(1).perPage(20));
+
+      assertEquals(size(droplets), 1);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/droplets?page=1&per_page=20");
+   }
+
+   public void testListDropletsWithOptionsReturns404() throws 
InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Droplet> droplets = api.dropletApi().list(page(1).perPage(20));
+
+      assertTrue(isEmpty(droplets));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/droplets?page=1&per_page=20");
+   }
+   
+   public void testGetDroplet() throws InterruptedException {
+      server.enqueue(jsonResponse("/droplet.json"));
+
+      Droplet droplet = api.dropletApi().get(1);
+
+      assertEquals(droplet, dropletFromResource("/droplet.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/droplets/1");
+   }
+
+   public void testGetDropletReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Droplet droplet = api.dropletApi().get(1);
+
+      assertNull(droplet);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/droplets/1");
+   }
+   
+   public void testCreateDroplet() throws InterruptedException {
+      server.enqueue(jsonResponse("/droplet-create-res.json"));
+
+      DropletCreate droplet = api.dropletApi().create("digitalocean2-s-d5e", 
"sfo1", "512mb", "6374124", 
CreateDropletOptions.builder().addSshKeyId(421192).build());
+
+      assertEquals(droplet, objectFromResource("/droplet-create-res.json", 
DropletCreate.class));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/droplets", 
stringFromResource("/droplet-create-req.json"));
+   }
+   
+   public void testListKernels() throws InterruptedException {
+      server.enqueue(jsonResponse("/kernels-first.json"));
+      server.enqueue(jsonResponse("/kernels-last.json"));
+
+      Iterable<Kernel> kernels = 
api.dropletApi().listKernels(5425561).concat();
+
+      assertEquals(size(kernels), 10); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/droplets/5425561/kernels");
+      assertSent(server, "GET", "/droplets/5425561/kernels?page=2");
+   }
+
+   public void testListKernelsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Kernel> kernels = 
api.dropletApi().listKernels(5425561).concat();
+
+      assertTrue(isEmpty(kernels));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/droplets/5425561/kernels");
+   }
+   
+   public void testListKernelsWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/kernels-first.json"));
+
+      Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561, 
page(1).perPage(20));
+
+      assertEquals(size(kernels), 5);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", 
"/droplets/5425561/kernels?page=1&per_page=20");
+   }
+
+   public void testListKernelsWithOptionsReturns404() throws 
InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Kernel> kernels = api.dropletApi().listKernels(5425561, 
page(1).perPage(20));
+
+      assertTrue(isEmpty(kernels));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", 
"/droplets/5425561/kernels?page=1&per_page=20");
+   }
+   
+   public void testListActions() throws InterruptedException {
+      server.enqueue(jsonResponse("/actions-first.json"));
+      server.enqueue(jsonResponse("/actions-last.json"));
+
+      Iterable<Action> actions = api.dropletApi().listActions(1).concat();
+
+      assertEquals(size(actions), 8); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/droplets/1/actions");
+      assertSent(server, "GET", "/droplets/1/actions?page=2&per_page=5");
+   }
+
+   public void testListActionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Action> actions = api.dropletApi().listActions(1).concat();
+
+      assertTrue(isEmpty(actions));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/droplets/1/actions");
+   }
+
+   public void testListActionsWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/actions-first.json"));
+
+      Iterable<Action> actions = api.dropletApi().listActions(1, 
page(1).perPage(5));
+
+      assertEquals(size(actions), 5);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/droplets/1/actions?page=1&per_page=5");
+   }
+
+   public void testListActionsWithOptionsReturns404() throws 
InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Action> actions = api.dropletApi().listActions(1, 
page(1).perPage(5));
+
+      assertTrue(isEmpty(actions));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/droplets/1/actions?page=1&per_page=5");
+   }
+   
+   public void testListBackups() throws InterruptedException {
+      server.enqueue(jsonResponse("/backups-first.json"));
+      server.enqueue(jsonResponse("/backups-last.json"));
+
+      Iterable<Backup> backups = 
api.dropletApi().listBackups(5425561).concat();
+
+      assertEquals(size(backups), 2); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/droplets/5425561/backups");
+      assertSent(server, "GET", "/droplets/5425561/backups?page=2");
+   }
+
+   public void testListBackupsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Backup> backups = 
api.dropletApi().listBackups(5425561).concat();
+
+      assertTrue(isEmpty(backups));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/droplets/5425561/backups");
+   }
+   
+   public void testListBackupsWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/backups-first.json"));
+
+      Iterable<Backup> backups = api.dropletApi().listBackups(5425561, 
page(1).perPage(20));
+
+      assertEquals(size(backups), 1);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", 
"/droplets/5425561/backups?page=1&per_page=20");
+   }
+
+   public void testListBackupsWithOptionsReturns404() throws 
InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Backup> backups = api.dropletApi().listBackups(5425561, 
page(1).perPage(20));
+
+      assertTrue(isEmpty(backups));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", 
"/droplets/5425561/backups?page=1&per_page=20");
+   }
+   
+   public void testListSnapshots() throws InterruptedException {
+      server.enqueue(jsonResponse("/snapshots-first.json"));
+      server.enqueue(jsonResponse("/snapshots-last.json"));
+
+      Iterable<Snapshot> snapshots = 
api.dropletApi().listSnapshots(5425561).concat();
+
+      assertEquals(size(snapshots), 2); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/droplets/5425561/snapshots");
+      assertSent(server, "GET", "/droplets/5425561/snapshots?page=2");
+   }
+
+   public void testListSnapshotsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Snapshot> snapshots = 
api.dropletApi().listSnapshots(5425561).concat();
+
+      assertTrue(isEmpty(snapshots));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/droplets/5425561/snapshots");
+   }
+   
+   public void testListSnapshotsWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/snapshots-first.json"));
+
+      Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561, 
page(1).perPage(20));
+
+      assertEquals(size(snapshots), 1);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", 
"/droplets/5425561/snapshots?page=1&per_page=20");
+   }
+
+   public void testListSnapshotsWithOptionsReturns404() throws 
InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Snapshot> snapshots = api.dropletApi().listSnapshots(5425561, 
page(1).perPage(20));
+
+      assertTrue(isEmpty(snapshots));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", 
"/droplets/5425561/snapshots?page=1&per_page=20");
+   }
+   
+   public void testDeleteDroplet() throws InterruptedException {
+      server.enqueue(response204());
+
+      api.dropletApi().delete(1);
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/droplets/1");
+   }
+
+   public void testDeleteDropletReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      api.dropletApi().delete(1);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/droplets/1");
+   }
+   
+   public void testPowerCycleDroplet() throws InterruptedException {
+      server.enqueue(jsonResponse("/power-cycle.json"));
+
+      Action action = api.dropletApi().powerCycle(1);
+      
+      assertEquals(action, actionFromResource("/power-cycle.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/droplets/1/actions", 
"{\"type\":\"power_cycle\"}");
+   }
+   
+   public void testPowerOn() throws InterruptedException {
+      server.enqueue(jsonResponse("/power-on.json"));
+
+      Action action = api.dropletApi().powerOn(1);
+      
+      assertEquals(action, actionFromResource("/power-on.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/droplets/1/actions", 
"{\"type\":\"power_on\"}");
+   }
+   
+   public void testPowerOff() throws InterruptedException {
+      server.enqueue(jsonResponse("/power-off.json"));
+
+      Action action = api.dropletApi().powerOff(1);
+      
+      assertEquals(action, actionFromResource("/power-off.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/droplets/1/actions", 
"{\"type\":\"power_off\"}");
+   }
+   
+   public void testReboot() throws InterruptedException {
+      server.enqueue(jsonResponse("/reboot.json"));
+
+      Action action = api.dropletApi().reboot(1);
+      
+      assertEquals(action, actionFromResource("/reboot.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/droplets/1/actions", 
"{\"type\":\"reboot\"}");
+   }
+   
+   public void testShutdown() throws InterruptedException {
+      server.enqueue(jsonResponse("/shutdown.json"));
+
+      Action action = api.dropletApi().shutdown(1);
+      
+      assertEquals(action, actionFromResource("/shutdown.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/droplets/1/actions", 
"{\"type\":\"shutdown\"}");
+   }
+   
+   public void testSnapshot() throws InterruptedException {
+      server.enqueue(jsonResponse("/snapshot.json"));
+
+      Action action = api.dropletApi().snapshot(1, "foo");
+      
+      assertEquals(action, actionFromResource("/snapshot.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/droplets/1/actions", 
"{\"type\":\"snapshot\",\"name\":\"foo\"}");
+   }
+   
+   private Droplet dropletFromResource(String resource) {
+      return onlyObjectFromResource(resource, new TypeToken<Map<String, 
Droplet>>() {
+         private static final long serialVersionUID = 1L;
+      }); 
+   }
+   
+   private Action actionFromResource(String resource) {
+      return onlyObjectFromResource(resource, new TypeToken<Map<String, 
Action>>() {
+         private static final long serialVersionUID = 1L;
+      }); 
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiLiveTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiLiveTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiLiveTest.java
new file mode 100644
index 0000000..8c4c96e
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiLiveTest.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.features;
+
+import static 
org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.page;
+import static 
org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.type;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
+@Test(groups = "live", testName = "ImageApiLiveTest")
+public class ImageApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+   public void testListImages() {
+      final AtomicInteger found = new AtomicInteger(0);
+      // DigitalOcean return 25 records per page by default. Inspect at most 2 
pages
+      assertTrue(api().list().concat().limit(50).allMatch(new 
Predicate<Image>() {
+         @Override
+         public boolean apply(Image input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.name());
+         }
+      }), "All images must have the 'name' field populated");
+      assertTrue(found.get() > 0, "Expected some images to be returned");
+   }
+   
+   public void testListImagesOnePage() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(page(1).perPage(5)).allMatch(new 
Predicate<Image>() {
+         @Override
+         public boolean apply(Image input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.name());
+         }
+      }), "All images must have the 'name' field populated");
+      assertTrue(found.get() > 0, "Expected some images to be returned");
+   }
+   
+   public void testListImagesByType() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(type("distribution").perPage(5)).allMatch(new 
Predicate<Image>() {
+         @Override
+         public boolean apply(Image input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.distribution());
+         }
+      }), "All images must have the 'distribution' field populated");
+      assertTrue(found.get() > 0, "Expected some images to be returned");
+   }
+   
+   public void testGetImage() {
+      Optional<Image> first = api().list().concat().first();
+      assertTrue(first.isPresent(), "At least one image was expected to 
exist");
+      assertNotNull(api().get(first.get().id()));
+   }
+   
+   public void testGetImageBySlug() {
+      Optional<Image> first = api().list().concat().firstMatch(new 
Predicate<Image>() {
+         @Override
+         public boolean apply(Image input) {
+            return !isNullOrEmpty(input.slug());
+         }
+      });
+      
+      assertTrue(first.isPresent(), "At least one image with the 'slug' field 
set was expected to exist");
+      assertNotNull(api().get(first.get().slug()));
+   }
+   
+   // TODO: Delete live test once the create/transfer operations are 
implemented
+   
+   private ImageApi api() {
+      return api.imageApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiMockTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiMockTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiMockTest.java
new file mode 100644
index 0000000..d172174
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/ImageApiMockTest.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static 
org.jclouds.digitalocean2.domain.options.ImageListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.digitalocean2.domain.Image;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "ImageApiMockTest", singleThreaded = true)
+public class ImageApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+   public void testListImages() throws InterruptedException {
+      server.enqueue(jsonResponse("/images-first.json"));
+      server.enqueue(jsonResponse("/images-last.json"));
+
+      Iterable<Image> images = api.imageApi().list().concat();
+
+      assertEquals(size(images), 10); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/images");
+      assertSent(server, "GET", "/images?page=2&per_page=5&type=distribution");
+   }
+
+   public void testListImagesReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Image> images = api.imageApi().list().concat();
+
+      assertTrue(isEmpty(images));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/images");
+   }
+
+   public void testListImagesWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/images-first.json"));
+
+      Iterable<Image> images = 
api.imageApi().list(page(1).perPage(5).type("distribution"));
+
+      assertEquals(size(images), 5);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/images?page=1&per_page=5&type=distribution");
+   }
+
+   public void testListImagesWithOptionsReturns404() throws 
InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Image> images = 
api.imageApi().list(page(1).perPage(5).type("distribution"));
+
+      assertTrue(isEmpty(images));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/images?page=1&per_page=5&type=distribution");
+   }
+   
+   public void testGetImage() throws InterruptedException {
+      server.enqueue(jsonResponse("/image.json"));
+
+      Image image = api.imageApi().get(1);
+
+      assertEquals(image, imageFromResource("/image.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/images/1");
+   }
+
+   public void testGetImageReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Image image = api.imageApi().get(1);
+
+      assertNull(image);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/images/1");
+   }
+   
+   public void testGetImageUsingSlug() throws InterruptedException {
+      server.enqueue(jsonResponse("/image.json"));
+
+      Image image = api.imageApi().get("foo");
+
+      assertEquals(image, imageFromResource("/image.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/images/foo");
+   }
+
+   public void testGetImageUsingSlugReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Image image = api.imageApi().get("foo");
+
+      assertNull(image);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/images/foo");
+   }
+   
+   public void testDeleteImage() throws InterruptedException {
+      server.enqueue(response204());
+
+      api.imageApi().delete(1);
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/images/1");
+   }
+
+   public void testDeleteImageReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      api.imageApi().delete(1);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/images/1");
+   }
+   
+   private Image imageFromResource(String resource) {
+      return onlyObjectFromResource(resource, new TypeToken<Map<String, 
Image>>() {
+         private static final long serialVersionUID = 1L;
+      }); 
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
new file mode 100644
index 0000000..2911d79
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiLiveTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.features;
+
+import static 
org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.jclouds.digitalocean2.domain.Key;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import com.google.common.collect.FluentIterable;
+import com.google.common.io.Resources;
+
+@Test(groups = "live", testName = "KeyApiLiveTest")
+public class KeyApiLiveTest extends BaseDigitalOcean2ApiLiveTest {
+
+   private Key dsa;
+   private Key ecdsa;
+
+   public void testCreateKey() {
+      dsa = api().create("jclouds-test-dsa", loadKey("/ssh-dsa.pub"));
+      ecdsa = api().create("jclouds-test-ecdsa", loadKey("/ssh-ecdsa.pub"));
+      
+      assertEquals(dsa.name(), "jclouds-test-dsa");
+      assertEquals(ecdsa.name(), "jclouds-test-ecdsa");
+   }
+   
+   @Test(dependsOnMethods = "testCreateKey")
+   public void testListKeys() {
+      FluentIterable<Key> keys = api().list().concat();
+      assertTrue(keys.size() >= 2, "At least the two created keys must exist");
+   }
+   
+   @Test(dependsOnMethods = "testCreateKey")
+   public void testListKeysOnePAge() {
+      FluentIterable<Key> keys = api().list(page(1));
+      assertTrue(keys.size() >= 2, "At least the two created keys must exist");
+   }
+
+   @Test(dependsOnMethods = "testCreateKey")
+   public void testGetKey() {
+      assertEquals(api().get(dsa.id()).fingerprint(), dsa.fingerprint());
+      assertEquals(api().get(ecdsa.fingerprint()).id(), ecdsa.id());
+   }
+   
+   @Test(dependsOnMethods = "testCreateKey")
+   public void testUpdateKey() {
+      api().update(dsa.id(), "jclouds-test-dsa-updated");
+      assertEquals(api().get(dsa.id()).name(), "jclouds-test-dsa-updated");
+      api().update(dsa.fingerprint(), "jclouds-test-dsa-updated2");
+      assertEquals(api().get(dsa.id()).name(), "jclouds-test-dsa-updated2");
+   }
+
+   @AfterClass(alwaysRun = true)
+   public void testDeleteKey() {
+      if (dsa != null) {
+         api().delete(dsa.id());
+         FluentIterable<Key> keys = api().list().concat();
+         assertFalse(keys.contains(dsa), "dsa key must not be present in 
list");
+      }
+      if (ecdsa != null) {
+         api().delete(ecdsa.fingerprint());
+         FluentIterable<Key>  keys = api().list().concat();
+         assertFalse(keys.contains(ecdsa), "dsa key must not be present in 
list");
+      }
+   }
+   
+   private String loadKey(String resourceName) {
+      try {
+         return Resources.toString(getClass().getResource(resourceName), 
Charsets.UTF_8);
+      } catch (IOException e) {
+         throw Throwables.propagate(e);
+      }
+   }
+
+   private KeyApi api() {
+      return api.keyApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/057be8df/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiMockTest.java
----------------------------------------------------------------------
diff --git 
a/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiMockTest.java
 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiMockTest.java
new file mode 100644
index 0000000..2f9d5d3
--- /dev/null
+++ 
b/providers/digitalocean2/src/test/java/org/jclouds/digitalocean2/features/KeyApiMockTest.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.digitalocean2.features;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static 
org.jclouds.digitalocean2.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.jclouds.digitalocean2.domain.Key;
+import org.jclouds.digitalocean2.internal.BaseDigitalOcean2ApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "KeyApiMockTest", singleThreaded = true)
+public class KeyApiMockTest extends BaseDigitalOcean2ApiMockTest {
+
+   public void testListKeys() throws InterruptedException {
+      server.enqueue(jsonResponse("/keys-first.json"));
+      server.enqueue(jsonResponse("/keys-last.json"));
+
+      Iterable<Key> keys = api.keyApi().list().concat();
+
+      assertEquals(size(keys), 7); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/account/keys");
+      assertSent(server, "GET", "/account/keys?page=2&per_page=5");
+   }
+
+   public void testListKeysReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Key> keys = api.keyApi().list().concat();
+
+      assertTrue(isEmpty(keys));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/account/keys");
+   }
+
+   public void testListKeysWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/keys-first.json"));
+
+      Iterable<Key> keys = api.keyApi().list(page(1).perPage(5));
+
+      assertEquals(size(keys), 5);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/account/keys?page=1&per_page=5");
+   }
+
+   public void testListKeysWithOptionsReturns404() throws InterruptedException 
{
+      server.enqueue(response404());
+
+      Iterable<Key> keys = api.keyApi().list(page(1).perPage(5));
+
+      assertTrue(isEmpty(keys));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/account/keys?page=1&per_page=5");
+   }
+   
+   public void testCreateKey() throws InterruptedException {
+      server.enqueue(jsonResponse("/key.json").setStatus("HTTP/1.1 201 
Created"));
+      
+      String dsa = stringFromResource("/ssh-dsa.pub");
+      
+      Key key = api.keyApi().create("foo", dsa);
+      
+      assertEquals(key, keyFromResource("/key.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/account/keys", 
String.format("{\"name\":\"foo\", \"public_key\":\"%s\"}", dsa));
+   }
+   
+   public void testGetKey() throws InterruptedException {
+      server.enqueue(jsonResponse("/key.json"));
+
+      Key key = api.keyApi().get(1);
+
+      assertEquals(key, keyFromResource("/key.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/account/keys/1");
+   }
+
+   public void testGetKeyReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Key key = api.keyApi().get(1);
+
+      assertNull(key);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/account/keys/1");
+   }
+   
+   public void testGetKeyUsingFingerprint() throws InterruptedException {
+      server.enqueue(jsonResponse("/key.json"));
+
+      Key key = 
api.keyApi().get("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+
+      assertEquals(key, keyFromResource("/key.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", 
"/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+   }
+
+   public void testGetKeyUsingFingerprintReturns404() throws 
InterruptedException {
+      server.enqueue(response404());
+
+      Key key = 
api.keyApi().get("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+
+      assertNull(key);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", 
"/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+   }
+   
+   public void testUpdateKey() throws InterruptedException {
+      server.enqueue(jsonResponse("/key.json"));
+
+      Key key = api.keyApi().update(1, "foo");
+
+      assertEquals(key, keyFromResource("/key.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "PUT", "/account/keys/1", "{\"name\":\"foo\"}");
+   }
+   
+   public void testUpdateKeyUsingFingerprint() throws InterruptedException {
+      server.enqueue(jsonResponse("/key.json"));
+
+      Key key = 
api.keyApi().update("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90", "foo");
+
+      assertEquals(key, keyFromResource("/key.json"));
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "PUT", 
"/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90", 
"{\"name\":\"foo\"}");
+   }
+   
+   public void testDeleteKey() throws InterruptedException {
+      server.enqueue(response204());
+
+      api.keyApi().delete(1);
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/account/keys/1");
+   }
+
+   public void testDeleteKeyReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      api.keyApi().delete(1);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/account/keys/1");
+   }
+   
+   public void testDeleteKeyUsingFingerprint() throws InterruptedException {
+      server.enqueue(response204());
+
+      api.keyApi().delete("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+      
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", 
"/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+   }
+
+   public void testDeleteKeyUsingfingerprintReturns404() throws 
InterruptedException {
+      server.enqueue(response404());
+
+      api.keyApi().delete("1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", 
"/account/keys/1a:cc:9b:88:c8:4f:b8:77:96:15:d2:0c:95:86:ff:90");
+   }
+   
+   private Key keyFromResource(String resource) {
+      return onlyObjectFromResource(resource, new TypeToken<Map<String, 
Key>>() {
+         private static final long serialVersionUID = 1L;
+      }); 
+   }
+}

Reply via email to