You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2014/07/28 22:02:55 UTC

[04/10] Move jclouds-chef to the main jclouds repo

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java b/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java
new file mode 100644
index 0000000..9b1354e
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.chef.config;
+
+import static com.google.common.base.Objects.equal;
+import static org.testng.Assert.assertEquals;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import org.jclouds.chef.config.ChefParserModule.KeepLastRepeatedKeyMapTypeAdapterFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Unit tests for the {@link ChefParserModule} class.
+ */
+@Test(groups = "unit", testName = "ChefParserModuleTest")
+public class ChefParserModuleTest {
+
+   private static class KeyValue {
+      private final String key;
+      private final String value;
+
+      private KeyValue(String key, String value) {
+         this.key = key;
+         this.value = value;
+      }
+
+      @Override
+      public int hashCode() {
+         return Objects.hashCode(key, value);
+      }
+
+      @Override
+      public boolean equals(Object obj) {
+         if (this == obj)
+            return true;
+         if (obj == null || getClass() != obj.getClass())
+            return false;
+         KeyValue that = KeyValue.class.cast(obj);
+         return equal(this.key, that.key) && equal(this.value, that.value);
+      }
+   }
+
+   private Gson map = new GsonBuilder().registerTypeAdapterFactory(new KeepLastRepeatedKeyMapTypeAdapterFactory())
+         .create();
+   private Type mapType = new TypeToken<Map<String, String>>() {
+      private static final long serialVersionUID = 1L;
+   }.getType();
+   private Type mapkeyValueType = new TypeToken<Map<String, KeyValue>>() {
+      private static final long serialVersionUID = 1L;
+   }.getType();
+
+   public void testKeepLastRepeatedKeyMapTypeAdapter() {
+      Map<String, String> noNulls = map.fromJson("{\"value\":\"a test string!\"}", mapType);
+      assertEquals(noNulls, ImmutableMap.of("value", "a test string!"));
+      Map<String, String> withNull = map.fromJson("{\"value\":null}", mapType);
+      assertEquals(withNull, ImmutableMap.of());
+      Map<String, String> withEmpty = map.fromJson("{\"value\":\"\"}", mapType);
+      assertEquals(withEmpty, ImmutableMap.of("value", ""));
+      Map<String, KeyValue> keyValues = map.fromJson(
+            "{\"i-foo\":{\"key\":\"i-foo\",\"value\":\"foo\"},\"i-bar\":{\"key\":\"i-bar\",\"value\":\"bar\"}}",
+            mapkeyValueType);
+      assertEquals(keyValues,
+            ImmutableMap.of("i-foo", new KeyValue("i-foo", "foo"), "i-bar", new KeyValue("i-bar", "bar")));
+      Map<String, KeyValue> duplicates = map
+            .fromJson(
+                  "{\"i-foo\":{\"key\":\"i-foo\",\"value\":\"foo\", \"value\":\"foo2\"},\"i-bar\":{\"key\":\"i-bar\",\"value\":\"bar\",\"value\":\"bar2\"}}",
+                  mapkeyValueType);
+      assertEquals(duplicates,
+            ImmutableMap.of("i-foo", new KeyValue("i-foo", "foo2"), "i-bar", new KeyValue("i-bar", "bar2")));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java b/apis/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java
new file mode 100644
index 0000000..bfba190
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.chef.filters;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertEqualsNoOrder;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+
+import javax.inject.Provider;
+import javax.ws.rs.HttpMethod;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.http.internal.SignatureWire;
+import org.jclouds.logging.config.NullLoggingModule;
+import org.jclouds.rest.internal.BaseRestApiTest.MockModule;
+import org.jclouds.util.Strings2;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.net.HttpHeaders;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+
+@Test(groups = { "unit" })
+public class SignedHeaderAuthTest {
+
+   public static final String USER_ID = "spec-user";
+   public static final String BODY = "Spec Body";
+   // Base64.encode64(Digest::SHA1.digest("Spec Body")).chomp
+   public static final String HASHED_BODY = "DFteJZPVv6WKdQmMqZUQUumUyRs=";
+   public static final String TIMESTAMP_ISO8601 = "2009-01-01T12:00:00Z";
+
+   public static final String PATH = "/organizations/clownco";
+   // Base64.encode64(Digest::SHA1.digest("/organizations/clownco")).chomp
+
+   public static final String HASHED_CANONICAL_PATH = "YtBWDn1blGGuFIuKksdwXzHU9oE=";
+   public static final String REQUESTING_ACTOR_ID = "c0f8a68c52bffa1020222a56b23cccfa";
+
+   // Content hash is ???TODO
+   public static final String X_OPS_CONTENT_HASH = "DFteJZPVv6WKdQmMqZUQUumUyRs=";
+
+   public static final String[] X_OPS_AUTHORIZATION_LINES = new String[] {
+         "jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
+         "NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
+         "3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
+         "IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
+         "9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0", "utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w==" };
+
+   // We expect Mixlib::Authentication::SignedHeaderAuth//sign to return this
+   // if passed the BODY above.
+   public static final Multimap<String, String> EXPECTED_SIGN_RESULT = ImmutableMultimap.<String, String> builder()
+         .put("X-Ops-Content-Hash", X_OPS_CONTENT_HASH).put("X-Ops-Userid", USER_ID).put("X-Ops-Sign", "version=1.0")
+         .put("X-Ops-Authorization-1", X_OPS_AUTHORIZATION_LINES[0])
+         .put("X-Ops-Authorization-2", X_OPS_AUTHORIZATION_LINES[1])
+         .put("X-Ops-Authorization-3", X_OPS_AUTHORIZATION_LINES[2])
+         .put("X-Ops-Authorization-4", X_OPS_AUTHORIZATION_LINES[3])
+         .put("X-Ops-Authorization-5", X_OPS_AUTHORIZATION_LINES[4])
+         .put("X-Ops-Authorization-6", X_OPS_AUTHORIZATION_LINES[5]).put("X-Ops-Timestamp", TIMESTAMP_ISO8601).build();
+
+   // Content hash for empty string
+   public static final String X_OPS_CONTENT_HASH_EMPTY = "2jmj7l5rSw0yVb/vlWAYkK/YBwk=";
+   public static final Multimap<String, String> EXPECTED_SIGN_RESULT_EMPTY = ImmutableMultimap
+         .<String, String> builder().put("X-Ops-Content-Hash", X_OPS_CONTENT_HASH_EMPTY).put("X-Ops-Userid", USER_ID)
+         .put("X-Ops-Sign", "version=1.0")
+         .put("X-Ops-Authorization-1", "N6U75kopDK64cEFqrB6vw+PnubnXr0w5LQeXnIGNGLRP2LvifwIeisk7QxEx")
+         .put("X-Ops-Authorization-2", "mtpQOWAw8HvnWErjzuk9AvUsqVmWpv14ficvkaD79qsPMvbje+aLcIrCGT1P")
+         .put("X-Ops-Authorization-3", "3d2uvf4w7iqwzrIscPnkxLR6o6pymR90gvJXDPzV7Le0jbfD8kmZ8AAK0sGG")
+         .put("X-Ops-Authorization-4", "09F1ftW80bLatJTA66Cw2wBz261r6x/abZhIKFJFDWLzyQGJ8ZNOkUrDDtgI")
+         .put("X-Ops-Authorization-5", "svLVXpOJKZZfKunsElpWjjsyNt3k8vpI1Y4ANO8Eg2bmeCPeEK+YriGm5fbC")
+         .put("X-Ops-Authorization-6", "DzWNPylHJqMeGKVYwGQKpg62QDfe5yXh3wZLiQcXow==")
+         .put("X-Ops-Timestamp", TIMESTAMP_ISO8601).build();
+
+   public static String PUBLIC_KEY;
+   public static String PRIVATE_KEY;
+
+   static {
+      try {
+         PUBLIC_KEY = Strings2.toStringAndClose(SignedHeaderAuthTest.class.getResourceAsStream("/pubkey.txt"));
+
+         PRIVATE_KEY = Strings2.toStringAndClose(SignedHeaderAuthTest.class.getResourceAsStream("/privkey.txt"));
+      } catch (IOException e) {
+         Throwables.propagate(e);
+      }
+   }
+
+   @Test
+   void canonicalizedPathRemovesMultipleSlashes() {
+      assertEquals(signing_obj.canonicalPath("///"), "/");
+   }
+
+   @Test
+   void canonicalizedPathRemovesTrailingSlash() {
+      assertEquals(signing_obj.canonicalPath("/path/"), "/path");
+   }
+
+   @Test
+   void shouldGenerateTheCorrectStringToSignAndSignature() {
+
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost/" + PATH)
+            .payload(BODY).build();
+
+      String expected_string_to_sign = new StringBuilder().append("Method:POST").append("\n").append("Hashed Path:")
+            .append(HASHED_CANONICAL_PATH).append("\n").append("X-Ops-Content-Hash:").append(HASHED_BODY).append("\n")
+            .append("X-Ops-Timestamp:").append(TIMESTAMP_ISO8601).append("\n").append("X-Ops-UserId:").append(USER_ID)
+            .toString();
+
+      assertEquals(signing_obj.createStringToSign("POST", HASHED_CANONICAL_PATH, HASHED_BODY, TIMESTAMP_ISO8601),
+            expected_string_to_sign);
+      assertEquals(signing_obj.sign(expected_string_to_sign), Joiner.on("").join(X_OPS_AUTHORIZATION_LINES));
+
+      request = signing_obj.filter(request);
+      Multimap<String, String> headersWithoutContentLength = LinkedHashMultimap.create(request.getHeaders());
+      headersWithoutContentLength.removeAll(HttpHeaders.CONTENT_LENGTH);
+      assertEqualsNoOrder(headersWithoutContentLength.values().toArray(), EXPECTED_SIGN_RESULT.values().toArray());
+   }
+
+   @Test
+   void shouldGenerateTheCorrectStringToSignAndSignatureWithNoBody() {
+
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.DELETE).endpoint("http://localhost/" + PATH)
+            .build();
+
+      request = signing_obj.filter(request);
+      Multimap<String, String> headersWithoutContentLength = LinkedHashMultimap.create(request.getHeaders());
+      assertEqualsNoOrder(headersWithoutContentLength.entries().toArray(), EXPECTED_SIGN_RESULT_EMPTY.entries()
+            .toArray());
+   }
+
+   @Test
+   void shouldNotChokeWhenSigningARequestForAResourceWithALongName() {
+      StringBuilder path = new StringBuilder("nodes/");
+      for (int i = 0; i < 100; i++)
+         path.append('A');
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.PUT)
+            .endpoint("http://localhost/" + path.toString()).payload(BODY).build();
+
+      signing_obj.filter(request);
+   }
+
+   @Test
+   void shouldReplacePercentage3FWithQuestionMarkAtUrl() {
+      StringBuilder path = new StringBuilder("nodes/");
+      path.append("test/cookbooks/myCookBook%3Fnum_versions=5");
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.GET)
+            .endpoint("http://localhost/" + path.toString()).payload(BODY).build();
+      request = signing_obj.filter(request);
+      assertTrue(request.getRequestLine().contains("?num_versions=5"));
+   }
+
+   private SignedHeaderAuth signing_obj;
+
+   /**
+    * before class, as we need to ensure that the filter is threadsafe.
+    * 
+    * @throws IOException
+    * 
+    */
+   @BeforeClass
+   protected void createFilter() throws IOException {
+
+      Injector injector = ContextBuilder.newBuilder(new ChefApiMetadata()).credentials(USER_ID, PRIVATE_KEY)
+            .modules(ImmutableSet.<Module> of(new MockModule(), new NullLoggingModule())).buildInjector();
+
+      HttpUtils utils = injector.getInstance(HttpUtils.class);
+      Crypto crypto = injector.getInstance(Crypto.class);
+
+      Supplier<PrivateKey> privateKey = injector.getInstance(Key.get(new TypeLiteral<Supplier<PrivateKey>>() {
+      }));
+
+      signing_obj = new SignedHeaderAuth(new SignatureWire(),
+            Suppliers.ofInstance(new Credentials(USER_ID, PRIVATE_KEY)), privateKey, new Provider<String>() {
+
+               @Override
+               public String get() {
+                  return TIMESTAMP_ISO8601;
+               }
+
+            }, utils, crypto);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java
new file mode 100644
index 0000000..f326482
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.chef.functions;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.rest.annotations.Api;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "BootstrapConfigForGroupTest")
+public class BootstrapConfigForGroupTest {
+
+   @Test(expectedExceptions = IllegalStateException.class)
+   public void testWhenNoDatabagItem() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Client client = createMock(Client.class);
+
+      BootstrapConfigForGroup fn = new BootstrapConfigForGroup("jclouds", chefApi);
+
+      expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(null);
+
+      replay(client);
+      replay(chefApi);
+
+      fn.apply("foo");
+
+      verify(client);
+      verify(chefApi);
+   }
+
+   @Test
+   public void testReturnsItem() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Api api = createMock(Api.class);
+
+      BootstrapConfigForGroup fn = new BootstrapConfigForGroup("jclouds", chefApi);
+      DatabagItem config = new DatabagItem("foo",
+            "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}");
+
+      expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(config);
+
+      replay(api);
+      replay(chefApi);
+
+      assertEquals(fn.apply("foo"), config);
+
+      verify(api);
+      verify(chefApi);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ClientForGroupTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ClientForGroupTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ClientForGroupTest.java
new file mode 100644
index 0000000..4be873c
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ClientForGroupTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.chef.functions;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Client;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+@Test(groups = "unit", testName = "ClientForGroupTest")
+public class ClientForGroupTest {
+
+   public void testWhenNoClientsInList() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Client client = createMock(Client.class);
+      PrivateKey privateKey = createMock(PrivateKey.class);
+
+      ClientForGroup fn = new ClientForGroup(chefApi);
+
+      expect(chefApi.listClients()).andReturn(ImmutableSet.<String> of());
+      expect(chefApi.createClient("foo-client-00")).andReturn(client);
+      expect(client.getPrivateKey()).andReturn(privateKey);
+
+      replay(client);
+      replay(chefApi);
+
+      Client compare = fn.apply("foo");
+      assertEquals(compare.getClientname(), "foo-client-00");
+      assertEquals(compare.getName(), "foo-client-00");
+      assertEquals(compare.getPrivateKey(), privateKey);
+
+      verify(client);
+      verify(chefApi);
+   }
+
+   public void testWhenClientsInListAddsToEnd() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Client client = createMock(Client.class);
+      PrivateKey privateKey = createMock(PrivateKey.class);
+
+      ClientForGroup fn = new ClientForGroup(chefApi);
+
+      expect(chefApi.listClients()).andReturn(
+            ImmutableSet.<String> of("foo-client-00", "foo-client-01", "foo-client-02"));
+      expect(chefApi.createClient("foo-client-03")).andReturn(client);
+      expect(client.getPrivateKey()).andReturn(privateKey);
+
+      replay(client);
+      replay(chefApi);
+
+      Client compare = fn.apply("foo");
+      assertEquals(compare.getClientname(), "foo-client-03");
+      assertEquals(compare.getName(), "foo-client-03");
+      assertEquals(compare.getPrivateKey(), privateKey);
+
+      verify(client);
+      verify(chefApi);
+   }
+
+   public void testWhenClientsInListReplacesMissing() throws IOException {
+      ChefApi chefApi = createMock(ChefApi.class);
+      Client client = createMock(Client.class);
+      PrivateKey privateKey = createMock(PrivateKey.class);
+
+      ClientForGroup fn = new ClientForGroup(chefApi);
+
+      expect(chefApi.listClients()).andReturn(ImmutableSet.<String> of("foo-client-00", "foo-client-02"));
+      expect(chefApi.createClient("foo-client-01")).andReturn(client);
+      expect(client.getPrivateKey()).andReturn(privateKey);
+
+      replay(client);
+      replay(chefApi);
+
+      Client compare = fn.apply("foo");
+      assertEquals(compare.getClientname(), "foo-client-01");
+      assertEquals(compare.getName(), "foo-client-01");
+      assertEquals(compare.getPrivateKey(), privateKey);
+
+      verify(client);
+      verify(chefApi);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java
new file mode 100644
index 0000000..8792025
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.chef.functions;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.jclouds.chef.config.ChefProperties.CHEF_UPDATE_GEMS;
+import static org.jclouds.chef.config.ChefProperties.CHEF_UPDATE_GEM_SYSTEM;
+import static org.jclouds.chef.config.ChefProperties.CHEF_USE_OMNIBUS;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.PrivateKey;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefBootstrapModule;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.config.InstallChef;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.crypto.PemsTest;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.jclouds.scriptbuilder.domain.OsFamily;
+import org.jclouds.scriptbuilder.domain.ShellToken;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Functions;
+import com.google.common.base.Optional;
+import com.google.common.base.Suppliers;
+import com.google.common.cache.CacheLoader;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Resources;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.name.Names;
+
+@Test(groups = "unit", testName = "GroupToBootScriptTest")
+public class GroupToBootScriptTest {
+
+   private Json json;
+   private Statement installChefGems;
+   private Statement installChefOmnibus;
+   private Optional<String> validatorName;
+
+   @BeforeClass
+   public void setup() {
+      Injector injectorGems = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+            bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEM_SYSTEM)).toInstance("true");
+            bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEMS)).toInstance("true");
+            bind(String.class).annotatedWith(Names.named(CHEF_USE_OMNIBUS)).toInstance("false");
+         }
+      }, new ChefParserModule(), new GsonModule(), new ChefBootstrapModule());
+
+      Injector injectorOmnibus = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+            bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEM_SYSTEM)).toInstance("true");
+            bind(String.class).annotatedWith(Names.named(CHEF_UPDATE_GEMS)).toInstance("true");
+            bind(String.class).annotatedWith(Names.named(CHEF_USE_OMNIBUS)).toInstance("true");
+         }
+      }, new ChefParserModule(), new GsonModule(), new ChefBootstrapModule());
+
+      json = injectorGems.getInstance(Json.class);
+      installChefGems = injectorGems.getInstance(Key.get(Statement.class, InstallChef.class));
+      installChefOmnibus = injectorOmnibus.getInstance(Key.get(Statement.class, InstallChef.class));
+      validatorName = Optional.<String> of("chef-validator");
+   }
+
+   @Test(expectedExceptions = IllegalStateException.class)
+   public void testMustHaveValidatorName() {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of())), installChefGems,
+            Optional.<String> absent(), validatorCredential);
+      fn.apply("foo");
+   }
+
+   @Test(expectedExceptions = IllegalStateException.class)
+   public void testMustHaveValidatorCredential() {
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of())), installChefGems,
+            validatorName, Optional.<PrivateKey> absent());
+      fn.apply("foo");
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Key 'foo' not present in map")
+   public void testMustHaveRunScriptsName() {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of())), installChefGems,
+            validatorName, validatorCredential);
+      fn.apply("foo");
+   }
+
+   @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "null value in entry: foo=null")
+   public void testMustHaveRunScriptsValue() {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, DatabagItem> of("foo", (DatabagItem) null))),
+            installChefGems, validatorName, validatorCredential);
+      fn.apply("foo");
+   }
+
+   public void testOneRecipe() throws IOException {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
+                  "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))),
+            installChefGems, validatorName, validatorCredential);
+
+      PrivateKey validatorKey = validatorCredential.get();
+      expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
+      replay(validatorKey);
+
+      assertEquals(
+            fn.apply("foo").render(OsFamily.UNIX),
+            exitInsteadOfReturn(
+                  OsFamily.UNIX,
+                  Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)),
+                        Charsets.UTF_8)
+                        + Resources.toString(
+                              Resources.getResource("test_install_rubygems." + ShellToken.SH.to(OsFamily.UNIX)),
+                              Charsets.UTF_8)
+                        + "gem install chef --no-rdoc --no-ri\n"
+                        + Resources.toString(Resources.getResource("bootstrap.sh"), Charsets.UTF_8)));
+
+      verify(validatorKey);
+   }
+
+   public void testOneRecipeAndEnvironment() throws IOException {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
+                  "{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\","
+                        + "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), installChefGems,
+            validatorName, validatorCredential);
+
+      PrivateKey validatorKey = validatorCredential.get();
+      expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
+      replay(validatorKey);
+
+      assertEquals(
+            fn.apply("foo").render(OsFamily.UNIX),
+            exitInsteadOfReturn(
+                  OsFamily.UNIX,
+                  Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)),
+                        Charsets.UTF_8)
+                        + Resources.toString(
+                              Resources.getResource("test_install_rubygems." + ShellToken.SH.to(OsFamily.UNIX)),
+                              Charsets.UTF_8)
+                        + "gem install chef --no-rdoc --no-ri\n"
+                        + Resources.toString(Resources.getResource("bootstrap-env.sh"), Charsets.UTF_8)));
+
+      verify(validatorKey);
+   }
+
+   public void testOneRecipeOmnibus() throws IOException {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
+                  "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))),
+            installChefOmnibus, validatorName, validatorCredential);
+
+      PrivateKey validatorKey = validatorCredential.get();
+      expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
+      replay(validatorKey);
+
+      assertEquals(
+            fn.apply("foo").render(OsFamily.UNIX),
+            "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 "
+                  + "-X GET  https://www.opscode.com/chef/install.sh |(bash)\n"
+                  + Resources.toString(Resources.getResource("bootstrap.sh"), Charsets.UTF_8));
+
+      verify(validatorKey);
+   }
+
+   public void testOneRecipeAndEnvironmentOmnibus() throws IOException {
+      Optional<PrivateKey> validatorCredential = Optional.of(createMock(PrivateKey.class));
+      GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json,
+            CacheLoader.from(Functions.forMap(ImmutableMap.<String, JsonBall> of("foo", new JsonBall(
+                  "{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\","
+                        + "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), installChefOmnibus,
+            validatorName, validatorCredential);
+
+      PrivateKey validatorKey = validatorCredential.get();
+      expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes());
+      replay(validatorKey);
+
+      assertEquals(
+            fn.apply("foo").render(OsFamily.UNIX),
+            "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 "
+                  + "-X GET  https://www.opscode.com/chef/install.sh |(bash)\n"
+                  + Resources.toString(Resources.getResource("bootstrap-env.sh"), Charsets.UTF_8));
+
+      verify(validatorKey);
+   }
+
+   private static String exitInsteadOfReturn(OsFamily family, String input) {
+      return input.replaceAll(ShellToken.RETURN.to(family), ShellToken.EXIT.to(family));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java
new file mode 100644
index 0000000..e8bebcb
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseClientFromJsonTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.Client;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.crypto.Pems;
+import org.jclouds.encryption.internal.JCECrypto;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.io.Payloads;
+import org.jclouds.io.payloads.RSADecryptingPayload;
+import org.jclouds.io.payloads.RSAEncryptingPayload;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteSource;
+import com.google.common.io.ByteStreams;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code ParseClientFromJson}
+ */
+@Test(groups = { "unit" })
+public class ParseClientFromJsonTest {
+
+   private static final String PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAyb2ZJJqGm0KKR+8nfQJNsSd+F9tXNMV7CfOcW6jsqs8EZgiV\nR09hD1IYOj4YqM0qJONlgyg4xRWewdSG7QTPj1lJpVAida9sXy2+kzyagZA1Am0O\nZcbqb5hoeIDgcX+eDa79s0u0DomjcfO9EKhvHLBz+zM+3QqPRkPV8nYTbfs+HjVz\nzOU6D1B0XR3+IPZZl2AnWs2d0qhnStHcDUvnRVQ0P482YwN9VgceOZtpPz0DCKEJ\n5Tx5STub8k0/zt/VAMHQafLSuQMLd2s4ZLuOZptN//uAsTmxireqd37z+8ZTdBbJ\n8LEpJ+iCXuSfm5aUh7iw6oxvToY2AL53+jK2UQIDAQABAoIBAQDA88B3i/xWn0vX\nBVxFamCYoecuNjGwXXkSyZew616A+EOCu47bh4aTurdFbYL0YFaAtaWvzlaN2eHg\nDb+HDuTefE29+WkcGk6SshPmiz5T0XOCAICWw6wSVDkHmGwS4jZvbAFm7W8nwGk9\nYhxgxFiRngswJZFopOLoF5WXs2td8guIYNslMpo7tu50iFnBHwKO2ZsPAk8t9nnS\nxlDavKruymEmqHCr3+dtio5eaenJcp3fjoXBQOKUk3ipII29XRB8NqeCVV/7Kxwq\nckqOBEbRwBclckyIbD+RiAgKvOelORjEiE9R42vuqvxRA6k9kd9o7utlX0AUtpEn\n3gZc6LepAoGBAP9ael5Y75+sK2JJUNOOhO8ae45cdsilp2yI0X+UBaSuQs2+dyPp\nkpEHAxd4pmmSvn/8c9TlEZhr+qYbABXVPlDncxpIuw2Ajbk7s/S4XaSKsRqpXL57\nzj/QOqLkRk8+OVV9q6lMeQNqLtEj1u6JPviX70Ro+FQtRttNOYbfdP/fAoGB
 AMpA\nXjR5woV5sUb+REg9vEuYo8RSyOarxqKFCIXVUNsLOx+22+AK4+CQpbueWN7jotrl\nYD6uT6svWi3AAC7kiY0UI/fjVPRCUi8tVoQUE0TaU5VLITaYOB+W/bBaDE4M9560\n1NuDWO90baA5dfU44iuzva02rGJXK9+nS3o8nk/PAoGBALOL6djnDe4mwAaG6Jco\ncd4xr8jkyPzCRZuyBCSBbwphIUXLc7hDprPky064ncJD1UDmwIdkXd/fpMkg2QmA\n/CUk6LEFjMisqHojOaCL9gQZJPhLN5QUN2x1PJWGjs1vQh8Tkx0iUUCOa8bQPXNR\n+34OTsW6TUna4CSZAycLfhffAoGBAIggVsefBCvuQkF0NeUhmDCRZfhnd8y55RHR\n1HCvqKIlpv+rhcX/zmyBLuteopYyRJRsOiE2FW00i8+rIPRu4Z3Q5nybx7w3PzV9\noHN5R5baE9OyI4KpZWztpYYitZF67NcnAvVULHHOvVJQGnKYfLHJYmrJF7GA1ojM\nAuMdFbjFAoGAPxUhxwFy8gaqBahKUEZn4F81HFP5ihGhkT4QL6AFPO2e+JhIGjuR\n27+85hcFqQ+HHVtFsm81b/a+R7P4UuCRgc8eCjxQMoJ1Xl4n7VbjPbHMnIN0Ryvd\nO4ZpWDWYnCO021JTOUUOJ4J/y0416Bvkw0z59y7sNX7wDBBHHbK/XCc=\n-----END RSA PRIVATE KEY-----\n";
+   private static final String CERTIFICATE = "-----BEGIN CERTIFICATE-----\nMIIClzCCAgCgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxFjAUBgNVBAoM\nDU9wc2NvZGUsIEluYy4xHDAaBgNVBAsME0NlcnRpZmljYXRlIFNlcnZpY2UxMjAw\nBgNVBAMMKW9wc2NvZGUuY29tL2VtYWlsQWRkcmVzcz1hdXRoQG9wc2NvZGUuY29t\nMB4XDTEwMDczMDIwNDEzMFoXDTIwMDcyNzIwNDEzMFowADCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAMm9mSSahptCikfvJ30CTbEnfhfbVzTFewnznFuo\n7KrPBGYIlUdPYQ9SGDo+GKjNKiTjZYMoOMUVnsHUhu0Ez49ZSaVQInWvbF8tvpM8\nmoGQNQJtDmXG6m+YaHiA4HF/ng2u/bNLtA6Jo3HzvRCobxywc/szPt0Kj0ZD1fJ2\nE237Ph41c8zlOg9QdF0d/iD2WZdgJ1rNndKoZ0rR3A1L50VUND+PNmMDfVYHHjmb\naT89AwihCeU8eUk7m/JNP87f1QDB0Gny0rkDC3drOGS7jmabTf/7gLE5sYq3qnd+\n8/vGU3QWyfCxKSfogl7kn5uWlIe4sOqMb06GNgC+d/oytlECAwEAATANBgkqhkiG\n9w0BAQUFAAOBgQBftzSZxstWw60GqRTDNN/F2GnrdtnKBoXzHww3r6jtGEylYq20\n5KfKpEx+sPX0gyZuYJiXC2CkEjImAluWKcdN9ZF6VD541sheAjbiaU7q7ZsztTxF\nWUH2tCvHeDXYKPKek3QzL7bYpUhLnCN/XxEv6ibeMDwtI7f5qpk2Aspzcw==\n--
 ---END CERTIFICATE-----\n";
+   private ParseJson<Client> handler;
+   private Crypto crypto;
+   private PrivateKey privateKey;
+   private X509Certificate certificate;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException, CertificateException, InvalidKeySpecException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<Client>>() {
+      }));
+      crypto = injector.getInstance(Crypto.class);
+      certificate = Pems.x509Certificate(ByteSource.wrap(CERTIFICATE.getBytes(Charsets.UTF_8)), null);
+      privateKey = crypto.rsaKeyFactory().generatePrivate(Pems.privateKeySpec(ByteSource.wrap(PRIVATE_KEY.getBytes(Charsets.UTF_8))));
+   }
+
+   public void test() throws IOException, CertificateException, NoSuchAlgorithmException {
+
+      Client user = Client.builder().certificate(certificate).orgname("jclouds").clientname("adriancole-jcloudstest")
+            .name("adriancole-jcloudstest").isValidator(false).privateKey(privateKey).build();
+
+      byte[] encrypted = ByteStreams.toByteArray(new RSAEncryptingPayload(new JCECrypto(), Payloads.newPayload("fooya"), user
+            .getCertificate().getPublicKey()));
+
+      assertEquals(
+            ByteStreams.toByteArray(new RSADecryptingPayload(new JCECrypto(), Payloads.newPayload(encrypted), user.getPrivateKey())),
+            "fooya".getBytes());
+
+      assertEquals(
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+                  .payload(ParseClientFromJsonTest.class.getResourceAsStream("/client.json")).build()), user);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersionTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersionTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersionTest.java
new file mode 100644
index 0000000..300c8e0
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersionTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookDefinitionCheckingChefVersion}.
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookDefinitionCheckingChefVersionTest {
+
+   public void testParserFor09() {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance("0.9.8");
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      ParseCookbookDefinitionCheckingChefVersion parser = injector
+            .getInstance(ParseCookbookDefinitionCheckingChefVersion.class);
+      assertTrue(parser.parser instanceof ParseKeySetFromJson);
+   }
+
+   public void testParserFor010() {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance("0.10.8");
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      ParseCookbookDefinitionCheckingChefVersion parser = injector
+            .getInstance(ParseCookbookDefinitionCheckingChefVersion.class);
+      assertTrue(parser.parser instanceof ParseCookbookDefinitionFromJson);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonTest.java
new file mode 100644
index 0000000..736b283
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookDefinitionFromJson}.
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookDefinitionFromJsonTest {
+
+   private ParseCookbookDefinitionFromJson handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(ParseCookbookDefinitionFromJson.class);
+   }
+
+   public void testParse010Response() {
+      assertEquals(handler.apply(HttpResponse
+            .builder()
+            .statusCode(200)
+            .message("ok")
+            .payload(
+                  "{" + "\"apache2\" => {" + "\"url\" => \"http://localhost:4000/cookbooks/apache2\","
+                        + "\"versions\" => [" + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/5.1.0\","
+                        + "\"version\" => \"5.1.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/4.2.0\","
+                        + "\"version\" => \"4.2.0\"}" + "]" + "}," + "\"nginx\" => {"
+                        + "\"url\" => \"http://localhost:4000/cookbooks/nginx\"," + "\"versions\" => ["
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/nginx/1.0.0\"," + "\"version\" => \"1.0.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/nginx/0.3.0\"," + "\"version\" => \"0.3.0\"}"
+                        + "]" + "}" + "}").build()), ImmutableSet.of("apache2", "nginx"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java
new file mode 100644
index 0000000..b5d8a8b
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionFromJsonv10Test.java
@@ -0,0 +1,74 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookDefinitionFromJsonv10Test {
+
+   private ParseCookbookDefinitionFromJsonv10 handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(ParseCookbookDefinitionFromJsonv10.class);
+   }
+
+   public void testCookbokDefinitionParsing() throws URISyntaxException {
+      CookbookDefinition.Version v510 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/apache2/5.1.0")).version("5.1.0").build();
+      CookbookDefinition.Version v420 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/apache2/4.2.0")).version("4.2.0").build();
+      CookbookDefinition definition = CookbookDefinition.builder()
+            .name("apache2").url(new URI("http://localhost:4000/cookbooks/apache2")).version(v510).version(v420).build();
+
+      assertEquals(handler.apply(HttpResponse
+            .builder()
+            .statusCode(200)
+            .message("ok")
+            .payload(
+                  "{" + "\"apache2\" => {" + "\"url\" => \"http://localhost:4000/cookbooks/apache2\","
+                        + "\"versions\" => [" + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/5.1.0\","
+                        + "\"version\" => \"5.1.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/4.2.0\","
+                        + "\"version\" => \"4.2.0\"}" + "]" + "}" + "}").build()), definition);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java
new file mode 100644
index 0000000..04f8e35
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookDefinitionListFromJsonv10Test.java
@@ -0,0 +1,92 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+@Test(groups = {"unit"}, singleThreaded = true)
+public class ParseCookbookDefinitionListFromJsonv10Test {
+
+   private ParseCookbookDefinitionListFromJsonv10 handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(ParseCookbookDefinitionListFromJsonv10.class);
+   }
+
+   public void testCookbokDefinitionListParsing() throws URISyntaxException {
+      CookbookDefinition.Version v510 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/apache2/5.1.0")).version("5.1.0").build();
+      CookbookDefinition.Version v420 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/apache2/4.2.0")).version("4.2.0").build();
+      CookbookDefinition apache2 = CookbookDefinition.builder()
+            .name("apache2").url(new URI("http://localhost:4000/cookbooks/apache2")).version(v510).version(v420).build();
+      
+      CookbookDefinition.Version v100 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/nginx/1.0.0")).version("1.0.0").build();
+      CookbookDefinition.Version v030 = CookbookDefinition.Version.builder()
+            .url(new URI("http://localhost:4000/cookbooks/nginx/0.3.0")).version("0.3.0").build();
+      CookbookDefinition nginx = CookbookDefinition.builder()
+            .name("nginx").url(new URI("http://localhost:4000/cookbooks/nginx")).version(v100).version(v030).build();
+      
+      assertEquals(handler.apply(HttpResponse
+            .builder()
+            .statusCode(200)
+            .message("ok")
+            .payload(
+                  "{" + "\"apache2\" => {" + "\"url\" => \"http://localhost:4000/cookbooks/apache2\","
+                        + "\"versions\" => [" + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/5.1.0\","
+                        + "\"version\" => \"5.1.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/4.2.0\","
+                        + "\"version\" => \"4.2.0\"}" + "]" + "},"
+                        + "\"nginx\" => {"
+                        + "\"url\" => \"http://localhost:4000/cookbooks/nginx\","
+                        + "\"versions\" => ["
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/nginx/1.0.0\","
+                        + "\"version\" => \"1.0.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/nginx/0.3.0\","
+                        + "\"version\" => \"0.3.0\"}"
+                        + "]}" +
+                        "}").build()),
+            ImmutableSet.of(apache2, nginx));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java
new file mode 100644
index 0000000..7bc19bf
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionFromJsonTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.chef.functions;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.domain.Metadata;
+import org.jclouds.chef.domain.Resource;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code ParseCookbookVersionFromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookVersionFromJsonTest {
+
+   private ParseJson<CookbookVersion> handler;
+   private Injector injector;
+   private Json json;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      json = injector.getInstance(Json.class);
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<CookbookVersion>>() {
+      }));
+   }
+
+   public void testBrew() throws IOException {
+      CookbookVersion cookbook = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/brew-cookbook.json")).build());
+
+      assertEquals(cookbook,
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json.toJson(cookbook)).build()));
+   }
+
+   public void testTomcat() {
+      CookbookVersion cookbook = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/tomcat-cookbook.json")).build());
+
+      assertEquals(cookbook,
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json.toJson(cookbook)).build()));
+   }
+
+   public void testMysql() throws IOException {
+      CookbookVersion cookbook = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/mysql-cookbook.json")).build());
+
+      assertEquals(cookbook,
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json.toJson(cookbook)).build()));
+   }
+
+   public void testApache() {
+      CookbookVersion fromJson = handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+            .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/apache-chef-demo-cookbook.json"))
+            .build());
+
+      CookbookVersion expected = CookbookVersion
+            .builder("apache-chef-demo", "0.0.0")
+            .metadata(Metadata.builder() //
+                  .license("Apache v2.0") //
+                  .maintainer("Your Name") //
+                  .maintainerEmail("youremail@example.com") //
+                  .description("A fabulous new cookbook") //
+                  .version("0.0.0").name("apache-chef-demo") //
+                  .longDescription("") //
+                  .build())
+            .rootFile(
+                  Resource
+                        .builder()
+                        .name("README")
+                        .path("README")
+                        .checksum(base16().lowerCase().decode("11637f98942eafbf49c71b7f2f048b78"))
+                        .url(URI
+                              .create("https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-11637f98942eafbf49c71b7f2f048b78?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277766181&Signature=zgpNl6wSxjTNovqZu2nJq0JztU8%3D")) //
+                        .build())
+            .rootFile(
+                  Resource
+                        .builder()
+                        .name("Rakefile")
+                        .path("Rakefile")
+                        .checksum(base16().lowerCase().decode("ebcf925a1651b4e04b9cd8aac2bc54eb"))
+                        .url(URI
+                              .create("https://s3.amazonaws.com/opscode-platform-production-data/organization-486ca3ac66264fea926aa0b4ff74341c/checksum-ebcf925a1651b4e04b9cd8aac2bc54eb?AWSAccessKeyId=AKIAJOZTD2N26S7W6APA&Expires=1277766181&Signature=EFzzDSKKytTl7b%2FxrCeNLh05zj4%3D"))
+                        .build()) //
+            .build();
+
+      assertEquals(fromJson, expected);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersionTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersionTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersionTest.java
new file mode 100644
index 0000000..bad0edf
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersionTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookVersionsCheckingChefVersion}.
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookVersionsCheckingChefVersionTest {
+
+   public void testParserFor09() {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance("0.9.8");
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      ParseCookbookVersionsCheckingChefVersion parser = injector
+            .getInstance(ParseCookbookVersionsCheckingChefVersion.class);
+      assertTrue(parser.parser instanceof ParseCookbookVersionsV09FromJson);
+   }
+
+   public void testParserFor010() {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance("0.10.8");
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      ParseCookbookVersionsCheckingChefVersion parser = injector
+            .getInstance(ParseCookbookVersionsCheckingChefVersion.class);
+      assertTrue(parser.parser instanceof ParseCookbookVersionsV10FromJson);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJsonTest.java
new file mode 100644
index 0000000..08cf1d2
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJsonTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookVersionsV09FromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookVersionsV09FromJsonTest {
+
+   private ParseCookbookVersionsV09FromJson handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(ParseCookbookVersionsV09FromJson.class);
+   }
+
+   public void testRegex() {
+      assertEquals(
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+                  .payload("{\"apache2\": [\"0.1.8\", \"0.2\"]}").build()), ImmutableSet.of("0.1.8", "0.2"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJsonTest.java
new file mode 100644
index 0000000..e8b396a
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseCookbookVersionsV10FromJsonTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseCookbookVersionsV10FromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseCookbookVersionsV10FromJsonTest {
+
+   private ParseCookbookVersionsV10FromJson handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(ParseCookbookVersionsV10FromJson.class);
+   }
+
+   public void testRegex() {
+      assertEquals(handler.apply(HttpResponse
+            .builder()
+            .statusCode(200)
+            .message("ok")
+            .payload(
+                  "{" + "\"apache2\" => {" + "\"url\" => \"http://localhost:4000/cookbooks/apache2\","
+                        + "\"versions\" => [" + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/5.1.0\","
+                        + "\"version\" => \"5.1.0\"},"
+                        + "{\"url\" => \"http://localhost:4000/cookbooks/apache2/4.2.0\","
+                        + "\"version\" => \"4.2.0\"}" + "]" + "}" + "}").build()), ImmutableSet.of("5.1.0", "4.2.0"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseDataBagItemFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseDataBagItemFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseDataBagItemFromJsonTest.java
new file mode 100644
index 0000000..5dde523
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseDataBagItemFromJsonTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+@Test(groups = { "unit" })
+public class ParseDataBagItemFromJsonTest {
+   private ParseJson<DatabagItem> handler;
+   private Json mapper;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<DatabagItem>>() {
+      }));
+      mapper = injector.getInstance(Json.class);
+   }
+
+   public void test1() {
+      String json = "{\"my_key\":\"my_data\",\"id\":\"item1\"}";
+      DatabagItem item = new DatabagItem("item1", json);
+      assertEquals(handler.apply(HttpResponse.builder().statusCode(200).message("ok").payload(json).build()), item);
+      assertEquals(mapper.toJson(item), json);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBodyTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBodyTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBodyTest.java
new file mode 100644
index 0000000..940e858
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseErrorFromJsonOrReturnBodyTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.net.UnknownHostException;
+
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ReturnStringIf2xx;
+import org.jclouds.util.Strings2;
+import org.testng.annotations.Test;
+
+@Test(groups = { "unit" })
+public class ParseErrorFromJsonOrReturnBodyTest {
+
+   @Test
+   public void testApplyInputStreamDetails() throws UnknownHostException {
+      InputStream is = Strings2
+            .toInputStream("{\"error\":[\"invalid tarball: tarball root must contain java-bytearray\"]}");
+
+      ParseErrorFromJsonOrReturnBody parser = new ParseErrorFromJsonOrReturnBody(new ReturnStringIf2xx());
+      String response = parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload(is).build());
+      assertEquals(response, "invalid tarball: tarball root must contain java-bytearray");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java
new file mode 100644
index 0000000..8c646b2
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ * Tests behavior of {@code ParseKeySetFromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseKeySetFromJsonTest {
+
+   private ParseKeySetFromJson handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(ParseKeySetFromJson.class);
+   }
+
+   public void testRegex() {
+      assertEquals(
+            handler.apply(HttpResponse
+                  .builder()
+                  .statusCode(200)
+                  .message("ok")
+                  .payload(
+                        "{\n\"opscode-validator\": \"https://api.opscode.com/...\", \"pimp-validator\": \"https://api.opscode.com/...\"}")
+                  .build()), ImmutableSet.of("opscode-validator", "pimp-validator"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java
new file mode 100644
index 0000000..72b4956
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseNodeFromJsonTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.domain.JsonBall;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code ParseNodeFromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseNodeFromJsonTest {
+
+   private ParseJson<Node> handler;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<Node>>() {
+      }));
+   }
+
+   public void test() {
+      Node node = Node.builder() //
+            .name("adrian-jcloudstest") //
+            .normalAttribute("tomcat6", new JsonBall("{\"ssl_port\":8433}")) //
+            .runListElement("recipe[java]") //
+            .environment("prod") //
+            .build();
+
+      assertEquals(
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+                  .payload(ParseCookbookVersionFromJsonTest.class.getResourceAsStream("/node.json")).build()), node);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java
new file mode 100644
index 0000000..dd31d38
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/functions/ParseSandboxFromJsonTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.chef.functions;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.chef.domain.Sandbox;
+import org.jclouds.date.DateService;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code ParseSandboxFromJson}
+ */
+@Test(groups = { "unit" }, singleThreaded = true)
+public class ParseSandboxFromJsonTest {
+
+   private ParseJson<Sandbox> handler;
+   private DateService dateService;
+
+   @BeforeTest
+   protected void setUpInjector() throws IOException {
+      Injector injector = Guice.createInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION);
+         }
+      }, new ChefParserModule(), new GsonModule());
+
+      handler = injector.getInstance(Key.get(new TypeLiteral<ParseJson<Sandbox>>() {
+      }));
+      dateService = injector.getInstance(DateService.class);
+   }
+
+   public void test() {
+      assertEquals(
+            handler.apply(HttpResponse.builder().statusCode(200).message("ok")
+                  .payload(ParseSandboxFromJsonTest.class.getResourceAsStream("/sandbox.json")).build()),
+            Sandbox.builder().rev("1-8c27b0ea4c2b7aaedbb44cfbdfcc11b2").isCompleted(false)
+                  .createTime(dateService.iso8601SecondsDateParse("2010-07-07T03:36:00+00:00"))
+                  .name("f9d6d9b72bae465890aae87969f98a9c").guid("f9d6d9b72bae465890aae87969f98a9c").build());
+   }
+}