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:56 UTC

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

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/config/OhaiModule.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/config/OhaiModule.java b/apis/chef/src/main/java/org/jclouds/ohai/config/OhaiModule.java
new file mode 100644
index 0000000..650fe9e
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/config/OhaiModule.java
@@ -0,0 +1,183 @@
+/*
+ * 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.ohai.config;
+
+import static org.jclouds.chef.util.ChefUtils.ohaiAutomaticAttributeBinder;
+import static org.jclouds.chef.util.ChefUtils.toOhaiTime;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+import org.jclouds.ohai.Automatic;
+import org.jclouds.ohai.AutomaticSupplier;
+import org.jclouds.ohai.functions.ByteArrayToMacAddress;
+import org.jclouds.ohai.functions.MapSetToMultimap;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Multimap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.multibindings.MapBinder;
+
+/**
+ * Wires the components needed to parse ohai data
+ */
+@ConfiguresOhai
+public class OhaiModule extends AbstractModule {
+
+   @Override
+   protected void configure() {
+      bind(new TypeLiteral<Function<byte[], String>>() {
+      }).to(new TypeLiteral<ByteArrayToMacAddress>() {
+      });
+      bindOhai();
+   }
+
+   @Provides
+   @Automatic
+   protected Supplier<Map<String, JsonBall>> provideAutomatic(AutomaticSupplier in) {
+      return in;
+   }
+
+   @Provides
+   @Automatic
+   Multimap<String, Supplier<JsonBall>> provideAutomatic(MapSetToMultimap<String, Supplier<JsonBall>> converter,
+         @Automatic Map<String, Set<Supplier<JsonBall>>> input) {
+      return converter.apply(input);
+
+   }
+
+   @Named("systemProperties")
+   @Provides
+   protected Properties systemProperties() {
+      return System.getProperties();
+   }
+
+   public MapBinder<String, Supplier<JsonBall>> bindOhai() {
+      MapBinder<String, Supplier<JsonBall>> mapbinder = ohaiAutomaticAttributeBinder(binder()).permitDuplicates();
+      mapbinder.addBinding("ohai_time").to(OhaiTimeProvider.class);
+      mapbinder.addBinding("jvm/system").to(SystemPropertiesProvider.class);
+      mapbinder.addBinding("platform").to(PlatformProvider.class);
+      mapbinder.addBinding("platform_version").to(PlatformVersionProvider.class);
+      mapbinder.addBinding("current_user").to(CurrentUserProvider.class);
+      return mapbinder;
+   }
+
+   @Singleton
+   public static class OhaiTimeProvider implements Supplier<JsonBall> {
+      private final Provider<Long> timeProvider;
+
+      @Inject
+      OhaiTimeProvider(Provider<Long> timeProvider) {
+         this.timeProvider = timeProvider;
+      }
+
+      @Override
+      public JsonBall get() {
+         return toOhaiTime(timeProvider.get());
+      }
+
+   }
+
+   @Provides
+   protected Long millis() {
+      return System.currentTimeMillis();
+   }
+
+   @Singleton
+   public static class SystemPropertiesProvider implements Supplier<JsonBall> {
+
+      private final Json json;
+      private final Properties systemProperties;
+
+      @Inject
+      SystemPropertiesProvider(Json json, @Named("systemProperties") Properties systemProperties) {
+         this.json = json;
+         this.systemProperties = systemProperties;
+      }
+
+      @Override
+      public JsonBall get() {
+         return new JsonBall(json.toJson(systemProperties));
+      }
+
+   }
+
+   @Singleton
+   public static class PlatformProvider extends SystemPropertyProvider {
+
+      @Inject
+      PlatformProvider(@Named("systemProperties") Properties systemProperties) {
+         super("os.name", systemProperties);
+      }
+
+      @Override
+      public JsonBall get() {
+         JsonBall returnValue = super.get();
+         return returnValue != null ? new JsonBall(returnValue.toString().replaceAll("[ -]", "").toLowerCase()) : null;
+      }
+
+   }
+
+   @Singleton
+   public static class PlatformVersionProvider extends SystemPropertyProvider {
+
+      @Inject
+      PlatformVersionProvider(@Named("systemProperties") Properties systemProperties) {
+         super("os.version", systemProperties);
+      }
+
+   }
+
+   @Singleton
+   public static class CurrentUserProvider extends SystemPropertyProvider {
+
+      @Inject
+      CurrentUserProvider(@Named("systemProperties") Properties systemProperties) {
+         super("user.name", systemProperties);
+      }
+
+   }
+
+   public static class SystemPropertyProvider implements Supplier<JsonBall> {
+      private final Properties systemProperties;
+      private final String property;
+
+      @Inject
+      SystemPropertyProvider(String property, @Named("systemProperties") Properties systemProperties) {
+         this.property = property;
+         this.systemProperties = systemProperties;
+      }
+
+      @Override
+      public JsonBall get() {
+         return systemProperties.containsKey(property) ? new JsonBall(systemProperties.getProperty(property)) : null;
+      }
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/functions/ByteArrayToMacAddress.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/functions/ByteArrayToMacAddress.java b/apis/chef/src/main/java/org/jclouds/ohai/functions/ByteArrayToMacAddress.java
new file mode 100644
index 0000000..be7e46a
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/functions/ByteArrayToMacAddress.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ohai.functions;
+
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Lists.partition;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.asList;
+import static com.google.common.primitives.Bytes.toArray;
+
+import java.util.List;
+
+import javax.inject.Singleton;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+
+/**
+ * 
+ * Creates a string in the form: {@code 00:26:bb:09:e6:c4 }
+ */
+@Singleton
+public class ByteArrayToMacAddress implements Function<byte[], String> {
+
+   @Override
+   public String apply(byte[] from) {
+      return Joiner.on(':').join(transform(partition(asList(from), 1), new Function<List<Byte>, String>() {
+
+         @Override
+         public String apply(List<Byte> from) {
+            return base16().lowerCase().encode(toArray(from));
+         }
+
+      }));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java b/apis/chef/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java
new file mode 100644
index 0000000..4ef3c87
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.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.ohai.functions;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.inject.Singleton;
+
+import com.google.common.base.Function;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+
+@Singleton
+public class MapSetToMultimap<K, V> implements Function<Map<K, Set<V>>, Multimap<K, V>> {
+
+   @Override
+   public Multimap<K, V> apply(Map<K, Set<V>> from) {
+      Multimap<K, V> returnV = LinkedHashMultimap.create();
+      for (Entry<K, Set<V>> entry : from.entrySet()) {
+         for (V value : entry.getValue())
+            returnV.put(entry.getKey(), value);
+      }
+      return returnV;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java b/apis/chef/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java
new file mode 100644
index 0000000..c709463
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java
@@ -0,0 +1,156 @@
+/*
+ * 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.ohai.functions;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.domain.JsonBall;
+import org.jclouds.json.Json;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.inject.TypeLiteral;
+
+@Singleton
+public class NestSlashKeys implements Function<Multimap<String, Supplier<JsonBall>>, Map<String, JsonBall>> {
+
+   private final Json json;
+
+   @Inject
+   NestSlashKeys(Json json) {
+      this.json = checkNotNull(json, "json");
+   }
+
+   @Override
+   public Map<String, JsonBall> apply(Multimap<String, Supplier<JsonBall>> from) {
+
+      Map<String, JsonBall> autoAttrs = mergeSameKeys(from);
+
+      Map<String, JsonBall> modifiableFlatMap = Maps.newLinkedHashMap(Maps.filterKeys(autoAttrs,
+            new Predicate<String>() {
+
+               @Override
+               public boolean apply(String input) {
+                  return input.indexOf('/') == -1;
+               }
+
+            }));
+      Map<String, JsonBall> withSlashesMap = Maps.difference(autoAttrs, modifiableFlatMap).entriesOnlyOnLeft();
+      for (Entry<String, JsonBall> entry : withSlashesMap.entrySet()) {
+         List<String> keyParts = Lists.newArrayList(Splitter.on('/').split(entry.getKey()));
+         JsonBall toInsert = entry.getValue();
+         try {
+            putUnderContext(keyParts, toInsert, modifiableFlatMap);
+         } catch (IllegalArgumentException e) {
+            throw new IllegalArgumentException("error inserting value in entry: " + entry.getKey(), e);
+         }
+      }
+      return modifiableFlatMap;
+   }
+
+   private Map<String, JsonBall> mergeSameKeys(Multimap<String, Supplier<JsonBall>> from) {
+      Map<String, JsonBall> merged = Maps.newLinkedHashMap();
+      for (Entry<String, Supplier<JsonBall>> entry : from.entries()) {
+         if (merged.containsKey(entry.getKey())) {
+            mergeAsPeer(entry.getKey(), entry.getValue().get(), merged);
+         } else {
+            merged.put(entry.getKey(), entry.getValue().get());
+         }
+      }
+      return merged;
+   }
+
+   @VisibleForTesting
+   void mergeAsPeer(String key, JsonBall value, Map<String, JsonBall> insertionContext) {
+      Map<String, JsonBall> immutableValueContext = json.fromJson(insertionContext.get(key).toString(), mapLiteral);
+      Map<String, JsonBall> valueContext = Maps.newHashMap(immutableValueContext);
+      Map<String, JsonBall> toPut = json.<Map<String, JsonBall>> fromJson(value.toString(), mapLiteral);
+      Set<String> uniques = Sets.difference(toPut.keySet(), valueContext.keySet());
+      for (String k : uniques) {
+         valueContext.put(k, toPut.get(k));
+      }
+      Set<String> conflicts = Sets.difference(toPut.keySet(), uniques);
+      for (String k : conflicts) {
+         JsonBall v = toPut.get(k);
+         if (v.toString().matches("^\\{.*\\}$")) {
+            mergeAsPeer(k, v, valueContext);
+         } else {
+            // replace
+            valueContext.put(k, v);
+         }
+      }
+      insertionContext.put(key, new JsonBall(json.toJson(valueContext, mapLiteral)));
+   }
+
+   /**
+    * @param keyParts
+    * @param toInsert
+    * @param destination
+    * @throws IllegalArgumentException
+    *            <p/>
+    *            if destination.get(keyParts(0)) is not a map *
+    *            <p/>
+    *            keyParts is zero length
+    */
+   void putUnderContext(List<String> keyParts, JsonBall toInsert, Map<String, JsonBall> destination) {
+      checkNotNull(keyParts, "keyParts");
+      checkArgument(keyParts.size() >= 1, "keyParts must contain at least one element");
+
+      checkNotNull(toInsert, "toInsert");
+      checkNotNull(destination, "destination");
+
+      String rootKey = keyParts.remove(0);
+      String rootValue = destination.containsKey(rootKey) ? destination.get(rootKey).toString() : "{}";
+
+      checkArgument(rootValue.matches("^\\{.*\\}$"), "value must be a hash: %s", rootValue);
+      Map<String, JsonBall> immutableInsertionContext = json.fromJson(rootValue, mapLiteral);
+      Map<String, JsonBall> insertionContext = Maps.newHashMap(immutableInsertionContext);
+      if (keyParts.size() == 1) {
+         if (!insertionContext.containsKey(keyParts.get(0))) {
+            insertionContext.put(keyParts.get(0), toInsert);
+         } else {
+            String key = keyParts.get(0);
+            mergeAsPeer(key, toInsert, insertionContext);
+         }
+      } else {
+         putUnderContext(keyParts, toInsert, insertionContext);
+      }
+      destination.put(rootKey, new JsonBall(json.toJson(insertionContext, mapLiteral)));
+   }
+
+   final Type mapLiteral = new TypeLiteral<Map<String, JsonBall>>() {
+   }.getType();
+   final Type listLiteral = new TypeLiteral<List<JsonBall>>() {
+   }.getType();
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java b/apis/chef/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java
new file mode 100644
index 0000000..9713902
--- /dev/null
+++ b/apis/chef/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java
@@ -0,0 +1,44 @@
+/*
+ * 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.ohai.suppliers;
+
+import java.lang.management.RuntimeMXBean;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.domain.JsonBall;
+
+import com.google.common.base.Supplier;
+
+@Singleton
+public class UptimeSecondsSupplier implements Supplier<JsonBall> {
+
+   @Inject
+   UptimeSecondsSupplier(RuntimeMXBean runtime) {
+      this.runtime = runtime;
+   }
+
+   private final RuntimeMXBean runtime;
+
+   @Override
+   public JsonBall get() {
+      long uptimeInSeconds = runtime.getUptime() / 1000;
+      return new JsonBall(uptimeInSeconds);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
----------------------------------------------------------------------
diff --git a/apis/chef/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/apis/chef/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
new file mode 100644
index 0000000..07e3240
--- /dev/null
+++ b/apis/chef/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
@@ -0,0 +1,2 @@
+org.jclouds.chef.test.TransientChefApiMetadata
+org.jclouds.chef.ChefApiMetadata
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/clojure/org/jclouds/chef_test.clj
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/clojure/org/jclouds/chef_test.clj b/apis/chef/src/test/clojure/org/jclouds/chef_test.clj
new file mode 100644
index 0000000..bc542f1
--- /dev/null
+++ b/apis/chef/src/test/clojure/org/jclouds/chef_test.clj
@@ -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.
+;
+
+(ns org.jclouds.chef-test
+  (:use [org.jclouds.chef] :reload-all)
+  (:use [clojure.test]))
+
+(defn clean-stub-fixture
+  "This should allow basic tests to easily be run with another service."
+  [service account key & options]
+  (fn [f]
+ (with-chef-service [(apply chef-service service account key options)]
+(doseq [databag (databags)]
+  (delete-databag databag))
+(f))))
+
+(use-fixtures :each (clean-stub-fixture "transientchef" "" ""))
+
+(deftest chef-service?-test
+  (is (chef-service? *chef*)))
+
+(deftest as-chef-service-test
+  (is (chef-service? (chef-service "transientchef" "" "")))
+  (is (chef-service? (as-chef-service *chef*)))
+  (is (chef-service? (as-chef-service (chef-context *chef*)))))
+
+(deftest create-existing-databag-test
+  (is (not (databag-exists? "")))
+  (create-databag "fred")
+  (is (databag-exists? "fred")))
+
+(deftest create-databag-test
+  (create-databag "fred")
+  (is (databag-exists? "fred")))
+
+(deftest databags-test
+  (is (empty? (databags)))
+  (create-databag "fred")
+  (is (= 1 (count (databags)))))
+
+(deftest databag-items-test
+  (create-databag "databag")
+  (is (empty? (databag-items "databag")))
+  (is (create-databag-item "databag" {:id "databag-item1" :value "databag-value1"}))
+  (is (create-databag-item "databag" {:id "databag-item2" :value "databag-value2"}))
+  (is (= 2 (count (databag-items "databag")))))
+
+(deftest databag-item-test
+  (create-databag "databag")
+  (is (create-databag-item "databag" {:id "databag-item1" :value "databag-value1"}))
+  (is (create-databag-item "databag" {:id "databag-item2" :value "databag-value2"}))
+  (is (= {:id "databag-item2" :value "databag-value2"} (databag-item "databag" "databag-item2"))))
+
+(deftest run-list-test
+  (update-run-list #{"recipe[foo]"} "tag")
+  (is (= ["recipe[foo]"] (run-list "tag"))))

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java b/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java
new file mode 100644
index 0000000..23013a9
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/BaseChefApiExpectTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import org.jclouds.chef.filters.SignedHeaderAuth;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.rest.internal.BaseRestApiExpectTest;
+
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Base class for Chef Api expect tests.
+ */
+public abstract class BaseChefApiExpectTest<S> extends BaseRestApiExpectTest<S> {
+   public 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/fAoGBA
 MpA\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";
+
+   protected SignedHeaderAuth signedHeaderAuth;
+
+   public BaseChefApiExpectTest() {
+      credential = PRIVATE_KEY;
+      signedHeaderAuth = createInjector(Functions.forMap(ImmutableMap.<HttpRequest, HttpResponse> of()),
+            createModule(), setupProperties()).getInstance(SignedHeaderAuth.class);
+   }
+
+   protected HttpRequest signed(HttpRequest input) {
+      return signedHeaderAuth.filter(input);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java
new file mode 100644
index 0000000..ea4070d
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiExpectTest.java
@@ -0,0 +1,279 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.chef.config.ChefHttpApiModule;
+import org.jclouds.chef.domain.CookbookDefinition;
+import org.jclouds.chef.domain.Role;
+import org.jclouds.chef.domain.SearchResult;
+import org.jclouds.chef.options.SearchOptions;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.rest.ConfiguresRestClient;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.inject.Module;
+
+/**
+ * Expect tests for the {@link ChefApi} class.
+ */
+@Test(groups = "unit", testName = "ChefApiExpectTest")
+public class ChefApiExpectTest extends BaseChefApiExpectTest<ChefApi> {
+   public ChefApiExpectTest() {
+     provider = "chef";
+   }
+
+   private HttpRequest.Builder<?> getHttpRequestBuilder(String method, String endPoint) {
+      return HttpRequest.builder() //
+                  .method(method) //
+                  .endpoint("http://localhost:4000" + endPoint) //
+                  .addHeader("X-Chef-Version", ChefApiMetadata.DEFAULT_API_VERSION) //
+                  .addHeader("Accept", MediaType.APPLICATION_JSON);
+   }
+
+   public void testListClientsReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/clients").build()),
+            HttpResponse.builder().statusCode(200) //
+                        .payload(payloadFromResourceWithContentType("/clients_list.json", MediaType.APPLICATION_JSON)) //
+                        .build());
+      Set<String> nodes = api.listClients();
+      assertEquals(nodes.size(), 3);
+      assertTrue(nodes.contains("adam"), String.format("Expected nodes to contain 'adam' but was: %s", nodes));
+   }
+
+   public void testListClientsReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/clients").build()),
+            HttpResponse.builder().statusCode(404)
+                  .build());
+      Set<String> clients = api.listClients();
+      assertTrue(clients.isEmpty(), String.format("Expected clients to be empty but was: %s", clients));
+   }
+
+   public void testListNodesReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/nodes").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/nodes_list.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> nodes = api.listNodes();
+      assertEquals(nodes.size(), 3);
+      assertTrue(nodes.contains("blah"), String.format("Expected nodes to contain 'blah' but was: %s", nodes));
+   }
+
+   public void testListNodesReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/nodes").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> nodes = api.listNodes();
+      assertTrue(nodes.isEmpty(), String.format("Expected nodes to be empty but was: %s", nodes));
+   }
+
+   public void testListRecipesInEnvironmentReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/recipes").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/environment_recipes.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> recipes = api.listRecipesInEnvironment("dev");
+      assertEquals(recipes.size(), 3);
+      assertTrue(recipes.contains("apache2"), String.format("Expected recipes to contain 'apache2' but was: %s", recipes));
+   }
+
+   public void testListRecipesInEnvironmentReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/recipes").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> recipes = api.listRecipesInEnvironment("dev");
+      assertTrue(recipes.isEmpty(), String.format("Expected recipes to be empty but was: %s", recipes));
+   }
+
+   public void testListNodesInEnvironmentReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/nodes").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/nodes_list.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> nodes = api.listNodesInEnvironment("dev");
+      assertEquals(nodes.size(), 3);
+      assertTrue(nodes.contains("blah"), String.format("Expected nodes to contain 'blah' but was: %s", nodes));
+   }
+
+   public void testListNodesInEnvironmentReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/nodes").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> nodes = api.listNodesInEnvironment("dev");
+      assertTrue(nodes.isEmpty(), String.format("Expected nodes to be empty but was: %s", nodes));
+   }
+
+   public void testListCookbooksReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/cookbooks").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/env_cookbooks.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> cookbooks = api.listCookbooks();
+      assertEquals(cookbooks.size(), 2);
+      assertTrue(cookbooks.contains("apache2"), String.format("Expected cookbooks to contain 'apache2' but was: %s", cookbooks));
+   }
+
+   public void testListCookbooksReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/cookbooks").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> cookbooks = api.listCookbooks();
+      assertTrue(cookbooks.isEmpty(), String.format("Expected cookbooks to be empty but was: %s", cookbooks));
+   }
+
+   public void testListCookbooksInEnvironmentReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/cookbooks").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/env_cookbooks.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<CookbookDefinition> cookbooks = api.listCookbooksInEnvironment("dev");
+      assertEquals(cookbooks.size(), 2);
+   }
+
+   public void testListCookbooksInEnvironmentReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/cookbooks").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<CookbookDefinition> cookbooks = api.listCookbooksInEnvironment("dev");
+      assertTrue(cookbooks.isEmpty(), String.format("Expected cookbooks to be empty but was: %s", cookbooks));
+   }
+
+   public void testListCookbooksInEnvironmentWithNumVersionReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/environments/dev/cookbooks").addQueryParam("num_versions", "2").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<CookbookDefinition> cookbooks = api.listCookbooksInEnvironment("dev", "2");
+      assertTrue(cookbooks.isEmpty(), String.format("Expected cookbooks to be empty but was: %s", cookbooks));
+   }
+
+   public void testSearchRolesReturnsValidResult() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/search/role").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/search_role.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      SearchResult<? extends Role> result = api.searchRoles();
+      assertEquals(result.size(), 1);
+      assertEquals(result.iterator().next().getName(), "webserver");
+   }
+
+   public void testSearchRolesReturnsEmptyResult() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/search/role").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/search_role_empty.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      SearchResult<? extends Role> result = api.searchRoles();
+      assertTrue(result.isEmpty(), String.format("Expected search result to be empty but was: %s", result));
+   }
+
+   public void testSearchRolesWithOptionsReturnsValidResult() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/search/role").addQueryParam("q", "name:webserver").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/search_role.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      SearchOptions options = SearchOptions.Builder.query("name:webserver");
+      SearchResult<? extends Role> result = api.searchRoles(options);
+      assertEquals(result.size(), 1);
+      assertEquals(result.iterator().next().getName(), "webserver");
+   }
+
+   public void testSearchRolesWithOptionsReturnsEmptyResult() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/search/role").addQueryParam("q", "name:dummy").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/search_role_empty.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      SearchOptions options = SearchOptions.Builder.query("name:dummy");
+      SearchResult<? extends Role> result = api.searchRoles(options);
+      assertTrue(result.isEmpty(), String.format("Expected search result to be empty but was: %s", result));
+   }
+
+   public void testListRolesReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/roles").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/roles_list.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> roles = api.listRoles();
+      assertEquals(roles.size(), 2);
+      assertTrue(roles.contains("webserver"), String.format("Expected roles to contain 'websever' but was: %s", roles));
+   }
+
+   public void testListRolesReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/roles").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> roles = api.listRoles();
+      assertTrue(roles.isEmpty(), String.format("Expected roles to be empty but was: %s", roles));
+   }
+
+   public void testListDatabagsReturnsValidSet() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/data").build()),
+            HttpResponse.builder().statusCode(200)
+                  .payload(payloadFromResourceWithContentType("/data_list.json", MediaType.APPLICATION_JSON)) //
+                  .build());
+      Set<String> databags = api.listDatabags();
+      assertEquals(databags.size(), 2);
+      assertTrue(databags.contains("applications"), String.format("Expected databags to contain 'applications' but was: %s", databags));
+   }
+
+   public void testListDatabagsReturnsEmptySetOn404() {
+      ChefApi api = requestSendsResponse(
+            signed(getHttpRequestBuilder("GET", "/data").build()),
+            HttpResponse.builder().statusCode(404).build());
+      Set<String> databags = api.listDatabags();
+      assertTrue(databags.isEmpty(), String.format("Expected databags to be empty but was: %s", databags));
+   }
+
+   @Override
+   protected Module createModule() {
+      return new TestChefRestClientModule();
+   }
+
+   @ConfiguresRestClient
+   static class TestChefRestClientModule extends ChefHttpApiModule {
+      @Override
+      protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
+         return "timestamp";
+      }
+   }
+
+   @Override
+   protected ChefApiMetadata createApiMetadata() {
+      return new ChefApiMetadata();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java
new file mode 100644
index 0000000..e4ad583
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import org.jclouds.chef.internal.BaseChefApiLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ * Tests behavior of {@code ChefApi} against a Chef Server <= 0.9.8.
+ */
+@Test(groups = { "live" })
+public class ChefApiLiveTest extends BaseChefApiLiveTest<ChefApi> {
+
+   protected ChefApiLiveTest() {
+      provider = "chef";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/ChefApiMetadataTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiMetadataTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiMetadataTest.java
new file mode 100644
index 0000000..a78c20a
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiMetadataTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import org.jclouds.View;
+import org.jclouds.rest.internal.BaseHttpApiMetadataTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "ChefApiMetadataTest")
+public class ChefApiMetadataTest extends BaseHttpApiMetadataTest {
+
+   // no config management abstraction, yet
+   public ChefApiMetadataTest() {
+      super(new ChefApiMetadata(), ImmutableSet.<TypeToken<? extends View>> of());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java
new file mode 100644
index 0000000..434c22c
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java
@@ -0,0 +1,741 @@
+/*
+ * 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;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.asList;
+import static org.jclouds.reflect.Reflection2.method;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Properties;
+import java.util.Set;
+
+import org.jclouds.Constants;
+import org.jclouds.Fallbacks.EmptySetOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.apis.ApiMetadata;
+import org.jclouds.chef.config.ChefHttpApiModule;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.chef.domain.DatabagItem;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.chef.domain.Resource;
+import org.jclouds.chef.domain.Role;
+import org.jclouds.chef.filters.SignedHeaderAuth;
+import org.jclouds.chef.filters.SignedHeaderAuthTest;
+import org.jclouds.chef.functions.ParseCookbookVersionsCheckingChefVersion;
+import org.jclouds.chef.functions.ParseKeySetFromJson;
+import org.jclouds.chef.functions.ParseSearchClientsFromJson;
+import org.jclouds.chef.functions.ParseSearchDatabagFromJson;
+import org.jclouds.chef.functions.ParseSearchNodesFromJson;
+import org.jclouds.chef.options.CreateClientOptions;
+import org.jclouds.chef.options.SearchOptions;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.fallbacks.MapHttp4xxCodesToExceptions;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.functions.ParseFirstJsonValueNamed;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.http.functions.ReleasePayloadAndReturn;
+import org.jclouds.http.functions.ReturnInputStream;
+import org.jclouds.io.Payload;
+import org.jclouds.io.payloads.StringPayload;
+import org.jclouds.reflect.Invocation;
+import org.jclouds.rest.ConfiguresRestClient;
+import org.jclouds.rest.internal.BaseAsyncApiTest;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.Invokable;
+import com.google.inject.Module;
+
+/**
+ * Tests annotation parsing of {@code ChefApi}.
+ */
+@Test(groups = { "unit" })
+public class ChefApiTest extends BaseAsyncApiTest<ChefApi> {
+
+   public void testCommitSandbox() throws SecurityException, NoSuchMethodException, IOException {
+
+      Invokable<?, ?> method = method(ChefApi.class, "commitSandbox", String.class, boolean.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("0189e76ccc476701d6b374e5a1a27347", true)));
+      assertRequestLineEquals(httpRequest,
+            "PUT http://localhost:4000/sandboxes/0189e76ccc476701d6b374e5a1a27347 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"is_completed\":true}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateUploadSandboxForChecksums() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createUploadSandboxForChecksums", Set.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList
+            .<Object> of(ImmutableSet.of(asList(base16().lowerCase().decode("0189e76ccc476701d6b374e5a1a27347")),
+                  asList(base16().lowerCase().decode("0c5ecd7788cf4f6c7de2a57193897a6c")), asList(base16().lowerCase()
+                        .decode("1dda05ed139664f1f89b9dec482b77c0"))))));
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/sandboxes HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest,
+            "{\"checksums\":{\"0189e76ccc476701d6b374e5a1a27347\":null,\"0c5ecd7788cf4f6c7de2a57193897a6c\":null,"
+                  + "\"1dda05ed139664f1f89b9dec482b77c0\":null}}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+   }
+
+   public void testUploadContent() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "uploadContent", URI.class, Payload.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(URI.create("http://foo/bar"), new StringPayload("{\"foo\": \"bar\"}"))));
+      assertRequestLineEquals(httpRequest, "PUT http://foo/bar HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"foo\": \"bar\"}", "application/x-binary", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testGetCookbook() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "getCookbook", String.class, String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("cookbook", "1.0.0")));
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/cookbooks/cookbook/1.0.0 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteCookbook() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteCookbook", String.class, String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("cookbook", "1.0.0")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/cookbooks/cookbook/1.0.0 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testUpdateCookbook() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "updateCookbook", String.class, String.class,
+            CookbookVersion.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("cookbook", "1.0.1", CookbookVersion.builder("cookbook", "1.0.1").build())));
+
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/cookbooks/cookbook/1.0.1 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest,
+            "{\"name\":\"cookbook-1.0.1\",\"definitions\":[],\"attributes\":[],\"files\":[],"
+                  + "\"metadata\":{\"suggestions\":{},\"dependencies\":{},\"conflicting\":{},\"providing\":{},"
+                  + "\"platforms\":{},\"recipes\":{},\"replacing\":{},"
+                  + "\"groupings\":{},\"attributes\":{},\"recommendations\":{}},"
+                  + "\"providers\":[],\"cookbook_name\":\"cookbook\",\"resources\":[],\"templates\":[],"
+                  + "\"libraries\":[],\"version\":\"1.0.1\","
+                  + "\"recipes\":[],\"root_files\":[],\"json_class\":\"Chef::CookbookVersion\","
+                  + "\"chef_type\":\"cookbook_version\"}", "application/json", false);
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testListVersionsOfCookbook() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "listVersionsOfCookbook", String.class);
+      GeneratedHttpRequest httpRequest = processor
+            .apply(Invocation.create(method, ImmutableList.<Object> of("apache2")));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/cookbooks/apache2 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseCookbookVersionsCheckingChefVersion.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteClient() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteClient", String.class);
+      GeneratedHttpRequest httpRequest = processor
+            .apply(Invocation.create(method, ImmutableList.<Object> of("client")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/clients/client HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateApi() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createClient", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("api")));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/clients HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"api\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateAdminApi() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createClient", String.class, CreateClientOptions.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("api", CreateClientOptions.Builder.admin())));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/clients HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"api\",\"admin\":true}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testGenerateKeyForClient() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "generateKeyForClient", String.class);
+      GeneratedHttpRequest httpRequest = processor
+            .apply(Invocation.create(method, ImmutableList.<Object> of("client")));
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/clients/client HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"client\", \"private_key\": true}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteNode() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteNode", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("node")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/nodes/node HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateNode() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createNode", Node.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(
+            method,
+            ImmutableList.<Object> of(Node.builder().name("testnode").runListElement("recipe[java]")
+                  .environment("_default").build())));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/nodes HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest,
+            "{\"name\":\"testnode\",\"normal\":{},\"override\":{},\"default\":{},\"automatic\":{},"
+                  + "\"run_list\":[\"recipe[java]\"],\"chef_environment\":\"_default\",\"json_class\":\"Chef::Node\","
+                  + "\"chef_type\":\"node\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testUpdateNode() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "updateNode", Node.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(
+            method,
+            ImmutableList.<Object> of(Node.builder().name("testnode").runListElement("recipe[java]")
+                  .environment("_default").build())));
+
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/nodes/testnode HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest,
+            "{\"name\":\"testnode\",\"normal\":{},\"override\":{},\"default\":{},\"automatic\":{},"
+                  + "\"run_list\":[\"recipe[java]\"],\"chef_environment\":\"_default\",\"json_class\":\"Chef::Node\","
+                  + "\"chef_type\":\"node\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteRole() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteRole", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("role")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/roles/role HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateRole() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createRole", Role.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(Role.builder().name("testrole").runListElement("recipe[java]").build())));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/roles HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"testrole\",\"override_attributes\":{},\"default_attributes\":{},"
+            + "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::Role\",\"chef_type\":\"role\"}",
+            "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testUpdateRole() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "updateRole", Role.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(Role.builder().name("testrole").runListElement("recipe[java]").build())));
+
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/roles/testrole HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"testrole\",\"override_attributes\":{},\"default_attributes\":{},"
+            + "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::Role\",\"chef_type\":\"role\"}",
+            "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteDatabag() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteDatabag", String.class);
+      GeneratedHttpRequest httpRequest = processor
+            .apply(Invocation.create(method, ImmutableList.<Object> of("databag")));
+      assertRequestLineEquals(httpRequest, "DELETE http://localhost:4000/data/databag HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, VoidOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateDatabag() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabag", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("name")));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"name\":\"name\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testDeleteDatabagItem() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "deleteDatabagItem", String.class, String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", "databagItem")));
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseFirstJsonValueNamed.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testCreateDatabagItemThrowsIllegalArgumentOnPrimitive() throws SecurityException, NoSuchMethodException,
+         IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "100"))));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(
+            httpRequest,
+            "{\"name\":\"testdatabagItem\",\"override_attributes\":{},\"default_attributes\":{},"
+                  + "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::DatabagItem\",\"chef_type\":\"databagItem\"}",
+            "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testCreateDatabagItemThrowsIllegalArgumentOnWrongId() throws SecurityException, NoSuchMethodException,
+         IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "{\"id\": \"item1\",\"my_key\": \"my_data\"}"))));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(
+            httpRequest,
+            "{\"name\":\"testdatabagItem\",\"override_attributes\":{},\"default_attributes\":{},"
+                  + "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::DatabagItem\",\"chef_type\":\"databagItem\"}",
+            "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateDatabagItem() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "{\"id\": \"id\",\"my_key\": \"my_data\"}"))));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"id\": \"id\",\"my_key\": \"my_data\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testCreateDatabagItemEvenWhenUserForgotId() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "createDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "{\"my_key\": \"my_data\"}"))));
+
+      assertRequestLineEquals(httpRequest, "POST http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, "{\"id\":\"id\",\"my_key\": \"my_data\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testUpdateDatabagItem() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "updateDatabagItem", String.class, DatabagItem.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("name", new DatabagItem("id", "{\"my_key\": \"my_data\"}"))));
+
+      assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/data/name/id HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+
+      assertPayloadEquals(httpRequest, "{\"id\":\"id\",\"my_key\": \"my_data\"}", "application/json", false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, null);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testListDatabagItems() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "listDatabagItems", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("name")));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/data/name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseKeySetFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testListSearchIndexes() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "listSearchIndexes");
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.of()));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseKeySetFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, EmptySetOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+
+   public void testSearchClients() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchClients");
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.of()));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/client HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchClientsFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchClientsWithOptions() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchClients", SearchOptions.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(SearchOptions.Builder.query("text").rows(5))));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/client?q=text&rows=5 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchClientsFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchNodes() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchNodes");
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.of()));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/node HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchNodesFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchNodesWithOptions() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchNodes", SearchOptions.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(SearchOptions.Builder.query("foo:foo").start(3))));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/node?q=foo%3Afoo&start=3 HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchNodesFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchDatabagItems() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchDatabagItems", String.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, ImmutableList.<Object> of("foo")));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/foo HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchDatabagFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testSearchDatabagItemsWithOptions() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "searchDatabagItems", String.class, SearchOptions.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of("foo", SearchOptions.Builder.query("bar").sort("name DESC"))));
+
+      assertRequestLineEquals(httpRequest, "GET http://localhost:4000/search/foo?q=bar&sort=name%20DESC HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ParseSearchDatabagFromJson.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, MapHttp4xxCodesToExceptions.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   public void testGetResourceContents() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(ChefApi.class, "getResourceContents", Resource.class);
+      GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
+            ImmutableList.<Object> of(Resource.builder().name("test").url(URI.create("http://foo/bar")).build())));
+
+      assertRequestLineEquals(httpRequest, "GET http://foo/bar HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+            + "-test\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest, ReturnInputStream.class);
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
+   @Override
+   protected void checkFilters(HttpRequest request) {
+      assertEquals(request.getFilters().size(), 1);
+      assertEquals(request.getFilters().get(0).getClass(), SignedHeaderAuth.class);
+   }
+
+   @Override
+   protected Module createModule() {
+      return new TestChefRestClientModule();
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      props.put(Constants.PROPERTY_API_VERSION, ChefApiMetadata.DEFAULT_API_VERSION + "-test");
+      return props;
+   }
+
+   @ConfiguresRestClient
+   static class TestChefRestClientModule extends ChefHttpApiModule {
+      @Override
+      protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
+         return "timestamp";
+      }
+   }
+
+   @Override
+   public ApiMetadata createApiMetadata() {
+      identity = "user";
+      credential = SignedHeaderAuthTest.PRIVATE_KEY;
+      return new ChefApiMetadata();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/867c7a40/apis/chef/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java
----------------------------------------------------------------------
diff --git a/apis/chef/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java b/apis/chef/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.java
new file mode 100644
index 0000000..7648732
--- /dev/null
+++ b/apis/chef/src/test/java/org/jclouds/chef/binders/BindHexEncodedMD5sToJsonPayloadTest.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.chef.binders;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.asList;
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+
+import javax.ws.rs.HttpMethod;
+
+import org.jclouds.chef.ChefApiMetadata;
+import org.jclouds.chef.config.ChefParserModule;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.rest.annotations.ApiVersion;
+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" })
+public class BindHexEncodedMD5sToJsonPayloadTest {
+
+   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());
+
+   BindChecksumsToJsonPayload binder = injector.getInstance(BindChecksumsToJsonPayload.class);
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testMustBeIterable() {
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost").build();
+      binder.bindToRequest(request, new File("foo"));
+   }
+
+   public void testCorrect() {
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost").build();
+      binder.bindToRequest(request,
+            ImmutableSet.of(asList(base16().lowerCase().decode("abddef")), asList(base16().lowerCase().decode("1234"))));
+      assertEquals(request.getPayload().getRawContent(), "{\"checksums\":{\"abddef\":null,\"1234\":null}}");
+   }
+
+   @Test(expectedExceptions = { NullPointerException.class, IllegalStateException.class })
+   public void testNullIsBad() {
+      HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost").build();
+      binder.bindToRequest(request, null);
+   }
+
+}