summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThom May <thom@chef.io>2017-01-25 15:51:46 +0000
committerThom May <thom@chef.io>2017-02-14 11:45:44 -0800
commit46f5722ef83d3d3603e23ac9525c49f4ed43621a (patch)
tree84724dc91d23b3ef7e5da8da14ffccb4cbc13709
parentd4b87e5e0f73d92836b4523907e17b1a88be9317 (diff)
downloadchef-tm/versioned_api.tar.gz
Make it easier to have a versioned factorytm/versioned_api
classes providing an API should include VersionedAPI, whilst the factory class includes VersionedAPIFactory. Signed-off-by: Thom May <thom@may.lt>
-rw-r--r--lib/chef/mixin/versioned_api.rb48
-rw-r--r--lib/chef/server_api_versions.rb4
-rw-r--r--spec/unit/http/api_versions_spec.rb69
-rw-r--r--spec/unit/mixin/versioned_api_spec.rb107
-rw-r--r--spec/unit/server_api_versions_spec.rb44
5 files changed, 258 insertions, 14 deletions
diff --git a/lib/chef/mixin/versioned_api.rb b/lib/chef/mixin/versioned_api.rb
index b8ec61c5f0..9c2f2f4cdb 100644
--- a/lib/chef/mixin/versioned_api.rb
+++ b/lib/chef/mixin/versioned_api.rb
@@ -19,31 +19,51 @@ class Chef
module Mixin
module VersionedAPI
- def self.included(base)
- # When this file is mixed in, make sure we also add the class methods
- base.send :extend, ClassMethods
+ def minimum_api_version(version = nil)
+ if version
+ @minimum_api_version = version
+ else
+ @minimum_api_version
+ end
end
- module ClassMethods
- def versioned_interfaces
- @versioned_interfaces ||= []
- end
+ end
- def add_api_version(klass)
- versioned_interfaces << klass
- end
+ module VersionedAPIFactory
+
+ def versioned_interfaces
+ @versioned_interfaces ||= []
end
- def select_api_version
- self.class.versioned_interfaces.select do |klass|
- version = klass.send(:supported_api_version)
+ def add_versioned_api_class(klass)
+ versioned_interfaces << klass
+ end
+
+ def versioned_api_class
+ versioned_interfaces.select do |klass|
+ version = klass.send(:minimum_api_version)
# min and max versions will be nil if we've not made a request to the server yet,
# in which case we'll just start with the highest version and see what happens
ServerAPIVersions.instance.min_server_version.nil? || (version >= ServerAPIVersions.instance.min_server_version && version <= ServerAPIVersions.instance.max_server_version)
end
- .sort { |a, b| a.send(:supported_api_version) <=> b.send(:supported_api_version) }
+ .sort { |a, b| a.send(:minimum_api_version) <=> b.send(:minimum_api_version) }
.last
end
+
+ def def_versioned_delegator(method)
+ line_no = __LINE__; str = %{
+ def self.#{method}(*args, &block)
+ versioned_api_class.__send__(:#{method}, *args, &block)
+ end
+ }
+ module_eval(str, __FILE__, line_no)
+ end
+
+ def new(*args)
+ object = versioned_api_class.allocate
+ object.send(:initialize, *args)
+ object
+ end
end
end
end
diff --git a/lib/chef/server_api_versions.rb b/lib/chef/server_api_versions.rb
index 91591875a4..68dfd5ac90 100644
--- a/lib/chef/server_api_versions.rb
+++ b/lib/chef/server_api_versions.rb
@@ -32,5 +32,9 @@ class Chef
def max_server_version
!@versions.nil? ? @versions["max_version"] : nil
end
+
+ def reset!
+ @versions = nil
+ end
end
end
diff --git a/spec/unit/http/api_versions_spec.rb b/spec/unit/http/api_versions_spec.rb
new file mode 100644
index 0000000000..79c97a1b69
--- /dev/null
+++ b/spec/unit/http/api_versions_spec.rb
@@ -0,0 +1,69 @@
+#
+# Copyright:: Copyright 2017, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed 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.
+#
+
+require "spec_helper"
+
+describe Chef::HTTP::APIVersions do
+ class TestVersionClient < Chef::HTTP
+ use Chef::HTTP::APIVersions
+ end
+
+ before do
+ Chef::ServerAPIVersions.instance.reset!
+ end
+
+ let(:method) { "GET" }
+ let(:url) { "http://dummy.com" }
+ let(:headers) { {} }
+ let(:data) { false }
+
+ let(:request) {}
+ let(:return_value) { "200" }
+
+ # Test Variables
+ let(:response_body) { "Thanks for checking in." }
+ let(:response_headers) do
+ {
+ "x-ops-server-api-version" => { "min_version" => 0, "max_version" => 2 },
+ }
+ end
+
+ let(:response) do
+ m = double("HttpResponse", :body => response_body)
+ allow(m).to receive(:key?).with("x-ops-server-api-version").and_return(true)
+ allow(m).to receive(:[]) do |key|
+ response_headers[key]
+ end
+
+ m
+ end
+
+ let(:middleware) do
+ client = TestVersionClient.new(url)
+ client.middlewares[0]
+ end
+
+ def run_api_version_handler
+ middleware.handle_request(method, url, headers, data)
+ middleware.handle_response(response, request, return_value)
+ end
+
+ it "correctly stores server api versions" do
+ run_api_version_handler
+ expect(Chef::ServerAPIVersions.instance.min_server_version).to eq(0)
+ end
+end
diff --git a/spec/unit/mixin/versioned_api_spec.rb b/spec/unit/mixin/versioned_api_spec.rb
new file mode 100644
index 0000000000..4f2418ca24
--- /dev/null
+++ b/spec/unit/mixin/versioned_api_spec.rb
@@ -0,0 +1,107 @@
+#
+# Copyright:: Copyright 2015-2017, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed 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.
+#
+
+require "spec_helper"
+require "chef/mixin/versioned_api"
+
+describe Chef::Mixin::VersionedAPI do
+ let(:dummy_class) { Class.new { extend Chef::Mixin::VersionedAPI } }
+
+ it "allows a class to declare the minimum supported API version" do
+ dummy_class.minimum_api_version 3
+ expect(dummy_class.minimum_api_version).to eq(3)
+ end
+end
+
+describe Chef::Mixin::VersionedAPIFactory do
+ class V1Class; extend Chef::Mixin::VersionedAPI; minimum_api_version 1; end
+ class V2Class; extend Chef::Mixin::VersionedAPI; minimum_api_version 2; end
+ class V3Class; extend Chef::Mixin::VersionedAPI; minimum_api_version 3; end
+
+ let(:factory_class) { Class.new { extend Chef::Mixin::VersionedAPIFactory } }
+
+ before do
+ Chef::ServerAPIVersions.instance.reset!
+ end
+
+ describe "#add_versioned_api_class" do
+ it "adds a target class" do
+ factory_class.add_versioned_api_class V1Class
+ expect(factory_class.versioned_interfaces).to eq([V1Class])
+ end
+
+ it "can be called many times" do
+ factory_class.add_versioned_api_class V1Class
+ factory_class.add_versioned_api_class V2Class
+ expect(factory_class.versioned_interfaces).to eq([V1Class, V2Class])
+ end
+ end
+
+ describe "#versioned_api_class" do
+ describe "with no known versions" do
+ it "with one class it returns that class" do
+ factory_class.add_versioned_api_class V2Class
+ expect(factory_class.versioned_api_class.minimum_api_version).to eq(2)
+ end
+
+ it "with many classes it returns the highest minimum version" do
+ factory_class.add_versioned_api_class V1Class
+ factory_class.add_versioned_api_class V2Class
+ factory_class.add_versioned_api_class V3Class
+ expect(factory_class.versioned_api_class.minimum_api_version).to eq(3)
+ end
+ end
+
+ describe "with a known version" do
+ it "with one class it returns that class" do
+ Chef::ServerAPIVersions.instance.set_versions({ "min_version" => 0, "max_version" => 2 })
+ factory_class.add_versioned_api_class V2Class
+ expect(factory_class.versioned_api_class.minimum_api_version).to eq(2)
+ end
+
+ it "with a maximum version it returns the highest possible versioned class" do
+ Chef::ServerAPIVersions.instance.set_versions({ "min_version" => 0, "max_version" => 2 })
+ factory_class.add_versioned_api_class V1Class
+ factory_class.add_versioned_api_class V2Class
+ factory_class.add_versioned_api_class V3Class
+ expect(factory_class.versioned_api_class.minimum_api_version).to eq(2)
+ end
+ end
+
+ it "with no classes it returns nil" do
+ expect(factory_class.versioned_api_class).to be_nil
+ end
+ end
+
+ describe "#new" do
+ it "creates an instance of the versioned class" do
+ factory_class.add_versioned_api_class V2Class
+ expect { factory_class.new }.to_not raise_error
+ expect(factory_class.new.class).to eq(V2Class)
+ end
+ end
+
+ describe "#def_versioned_delegator" do
+ it "delegates the method to the correct class" do
+ factory_class.add_versioned_api_class V2Class
+ factory_class.def_versioned_delegator("test_method")
+ expect(V2Class).to receive(:test_method).with("test message").and_return(true)
+
+ factory_class.test_method("test message")
+ end
+ end
+end
diff --git a/spec/unit/server_api_versions_spec.rb b/spec/unit/server_api_versions_spec.rb
new file mode 100644
index 0000000000..43445eb825
--- /dev/null
+++ b/spec/unit/server_api_versions_spec.rb
@@ -0,0 +1,44 @@
+#
+# Copyright:: Copyright 2017, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed 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.
+#
+
+require "spec_helper"
+
+describe Chef::ServerAPIVersions do
+ before do
+ Chef::ServerAPIVersions.instance.reset!
+ end
+
+ describe "#min_server_version" do
+ it "returns nil if no versions have been recorded" do
+ expect(Chef::ServerAPIVersions.instance.min_server_version).to be_nil
+ end
+ it "returns the correct value" do
+ Chef::ServerAPIVersions.instance.set_versions({ "min_version" => 0, "max_version" => 2 })
+ expect(Chef::ServerAPIVersions.instance.min_server_version).to eq(0)
+ end
+ end
+
+ describe "#max_server_version" do
+ it "returns nil if no versions have been recorded" do
+ expect(Chef::ServerAPIVersions.instance.max_server_version).to be_nil
+ end
+ it "returns the correct value" do
+ Chef::ServerAPIVersions.instance.set_versions({ "min_version" => 0, "max_version" => 2 })
+ expect(Chef::ServerAPIVersions.instance.max_server_version).to eq(2)
+ end
+ end
+end