From 46f5722ef83d3d3603e23ac9525c49f4ed43621a Mon Sep 17 00:00:00 2001 From: Thom May Date: Wed, 25 Jan 2017 15:51:46 +0000 Subject: Make it easier to have a versioned factory classes providing an API should include VersionedAPI, whilst the factory class includes VersionedAPIFactory. Signed-off-by: Thom May --- lib/chef/mixin/versioned_api.rb | 48 ++++++++++----- lib/chef/server_api_versions.rb | 4 ++ spec/unit/http/api_versions_spec.rb | 69 ++++++++++++++++++++++ spec/unit/mixin/versioned_api_spec.rb | 107 ++++++++++++++++++++++++++++++++++ spec/unit/server_api_versions_spec.rb | 44 ++++++++++++++ 5 files changed, 258 insertions(+), 14 deletions(-) create mode 100644 spec/unit/http/api_versions_spec.rb create mode 100644 spec/unit/mixin/versioned_api_spec.rb create mode 100644 spec/unit/server_api_versions_spec.rb 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 -- cgit v1.2.1