diff options
author | Rémy Coutable <remy@rymai.me> | 2018-10-17 21:08:20 +0300 |
---|---|---|
committer | Douglas Barbosa Alexandre <dbalexandre@gmail.com> | 2018-10-17 21:08:20 +0300 |
commit | b6f2f738c73b1dfe66be61e1b37ca21fa698cf1c (patch) | |
tree | b6b520d12c2051a6a1cdaa5741f48f6583e0cce8 /qa/spec/factory | |
parent | ab9cf561c230f1b6ec630215a9a9def53e14d764 (diff) |
First iteration to allow creating QA resources using the API
Diffstat (limited to 'qa/spec/factory')
-rw-r--r-- | qa/spec/factory/api_fabricator_spec.rb | 161 | ||||
-rw-r--r-- | qa/spec/factory/base_spec.rb | 149 | ||||
-rw-r--r-- | qa/spec/factory/dependency_spec.rb | 23 | ||||
-rw-r--r-- | qa/spec/factory/product_spec.rb | 74 |
4 files changed, 336 insertions, 71 deletions
diff --git a/qa/spec/factory/api_fabricator_spec.rb b/qa/spec/factory/api_fabricator_spec.rb new file mode 100644 index 00000000000..e5fbc064911 --- /dev/null +++ b/qa/spec/factory/api_fabricator_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +describe QA::Factory::ApiFabricator do + let(:factory_without_api_support) do + Class.new do + def self.name + 'FooBarFactory' + end + end + end + + let(:factory_with_api_support) do + Class.new do + def self.name + 'FooBarFactory' + end + + def api_get_path + '/foo' + end + + def api_post_path + '/bar' + end + + def api_post_body + { name: 'John Doe' } + end + end + end + + before do + allow(subject).to receive(:current_url).and_return('') + end + + subject { factory.tap { |f| f.include(described_class) }.new } + + describe '#api_support?' do + let(:api_client) { spy('Runtime::API::Client') } + let(:api_client_instance) { double('API Client') } + + context 'when factory does not support fabrication via the API' do + let(:factory) { factory_without_api_support } + + it 'returns false' do + expect(subject).not_to be_api_support + end + end + + context 'when factory supports fabrication via the API' do + let(:factory) { factory_with_api_support } + + it 'returns false' do + expect(subject).to be_api_support + end + end + end + + describe '#fabricate_via_api!' do + let(:api_client) { spy('Runtime::API::Client') } + let(:api_client_instance) { double('API Client') } + + before do + stub_const('QA::Runtime::API::Client', api_client) + + allow(api_client).to receive(:new).and_return(api_client_instance) + allow(api_client_instance).to receive(:personal_access_token).and_return('foo') + end + + context 'when factory does not support fabrication via the API' do + let(:factory) { factory_without_api_support } + + it 'raises a NotImplementedError exception' do + expect { subject.fabricate_via_api! }.to raise_error(NotImplementedError, "Factory FooBarFactory does not support fabrication via the API!") + end + end + + context 'when factory supports fabrication via the API' do + let(:factory) { factory_with_api_support } + let(:api_request) { spy('Runtime::API::Request') } + let(:resource_web_url) { 'http://example.org/api/v4/foo' } + let(:resource) { { id: 1, name: 'John Doe', web_url: resource_web_url } } + let(:raw_post) { double('Raw POST response', code: 201, body: resource.to_json) } + + before do + stub_const('QA::Runtime::API::Request', api_request) + + allow(api_request).to receive(:new).and_return(double(url: resource_web_url)) + end + + context 'when creating a resource' do + before do + allow(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + end + + it 'returns the resource URL' do + expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url)) + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + + expect(subject.fabricate_via_api!).to eq(resource_web_url) + end + + it 'populates api_resource with the resource' do + subject.fabricate_via_api! + + expect(subject.api_resource).to eq(resource) + end + + context 'when the POST fails' do + let(:post_response) { { error: "Name already taken." } } + let(:raw_post) { double('Raw POST response', code: 400, body: post_response.to_json) } + + it 'raises a ResourceFabricationFailedError exception' do + expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url)) + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + + expect { subject.fabricate_via_api! }.to raise_error(described_class::ResourceFabricationFailedError, "Fabrication of FooBarFactory using the API failed (400) with `#{raw_post}`.") + expect(subject.api_resource).to be_nil + end + end + end + + context '#transform_api_resource' do + let(:factory) do + Class.new do + def self.name + 'FooBarFactory' + end + + def api_get_path + '/foo' + end + + def api_post_path + '/bar' + end + + def api_post_body + { name: 'John Doe' } + end + + def transform_api_resource(resource) + resource[:new] = 'foobar' + resource + end + end + end + + let(:resource) { { existing: 'foo', web_url: resource_web_url } } + let(:transformed_resource) { { existing: 'foo', new: 'foobar', web_url: resource_web_url } } + + it 'transforms the resource' do + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + expect(subject).to receive(:transform_api_resource).with(resource).and_return(transformed_resource) + + subject.fabricate_via_api! + end + end + end + end +end diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb index 04e04886699..184802a7903 100644 --- a/qa/spec/factory/base_spec.rb +++ b/qa/spec/factory/base_spec.rb @@ -1,40 +1,117 @@ +# frozen_string_literal: true + describe QA::Factory::Base do + include Support::StubENV + let(:factory) { spy('factory') } let(:product) { spy('product') } + let(:product_location) { 'http://product_location' } - describe '.fabricate!' do - subject { Class.new(described_class) } + shared_context 'fabrication context' do + subject do + Class.new(described_class) do + def self.name + 'MyFactory' + end + end + end before do - allow(QA::Factory::Product).to receive(:new).and_return(product) - allow(QA::Factory::Product).to receive(:populate!).and_return(product) + allow(subject).to receive(:current_url).and_return(product_location) + allow(subject).to receive(:new).and_return(factory) + allow(QA::Factory::Product).to receive(:populate!).with(factory, product_location).and_return(product) end + end - it 'instantiates the factory and calls factory method' do - expect(subject).to receive(:new).and_return(factory) + shared_examples 'fabrication method' do |fabrication_method_called, actual_fabrication_method = nil| + let(:fabrication_method_used) { actual_fabrication_method || fabrication_method_called } - subject.fabricate!('something') + it 'yields factory before calling factory method' do + expect(factory).to receive(:something!).ordered + expect(factory).to receive(fabrication_method_used).ordered.and_return(product_location) - expect(factory).to have_received(:fabricate!).with('something') + subject.public_send(fabrication_method_called, factory: factory) do |factory| + factory.something! + end end - it 'returns fabrication product' do - allow(subject).to receive(:new).and_return(factory) + it 'does not log the factory and build method when VERBOSE=false' do + stub_env('VERBOSE', 'false') + expect(factory).to receive(fabrication_method_used).and_return(product_location) - result = subject.fabricate!('something') + expect { subject.public_send(fabrication_method_called, 'something', factory: factory) } + .not_to output.to_stdout + end + end + + describe '.fabricate!' do + context 'when factory does not support fabrication via the API' do + before do + expect(described_class).to receive(:fabricate_via_api!).and_raise(NotImplementedError) + end - expect(result).to eq product + it 'calls .fabricate_via_browser_ui!' do + expect(described_class).to receive(:fabricate_via_browser_ui!) + + described_class.fabricate! + end end - it 'yields factory before calling factory method' do - allow(subject).to receive(:new).and_return(factory) + context 'when factory supports fabrication via the API' do + it 'calls .fabricate_via_browser_ui!' do + expect(described_class).to receive(:fabricate_via_api!) - subject.fabricate! do |factory| - factory.something! + described_class.fabricate! end + end + end + + describe '.fabricate_via_api!' do + include_context 'fabrication context' + + it_behaves_like 'fabrication method', :fabricate_via_api! + + it 'instantiates the factory, calls factory method returns fabrication product' do + expect(factory).to receive(:fabricate_via_api!).and_return(product_location) - expect(factory).to have_received(:something!).ordered - expect(factory).to have_received(:fabricate!).ordered + result = subject.fabricate_via_api!(factory: factory, parents: []) + + expect(result).to eq(product) + end + + it 'logs the factory and build method when VERBOSE=true' do + stub_env('VERBOSE', 'true') + expect(factory).to receive(:fabricate_via_api!).and_return(product_location) + + expect { subject.fabricate_via_api!(factory: factory, parents: []) } + .to output(/==> Built a MyFactory via api with args \[\] in [\d\w\.\-]+/) + .to_stdout + end + end + + describe '.fabricate_via_browser_ui!' do + include_context 'fabrication context' + + it_behaves_like 'fabrication method', :fabricate_via_browser_ui!, :fabricate! + + it 'instantiates the factory and calls factory method' do + subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) + + expect(factory).to have_received(:fabricate!).with('something') + end + + it 'returns fabrication product' do + result = subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) + + expect(result).to eq(product) + end + + it 'logs the factory and build method when VERBOSE=true' do + stub_env('VERBOSE', 'true') + + expect { subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) } + .to output(/==> Built a MyFactory via browser_ui with args \["something"\] in [\d\w\.\-]+/) + .to_stdout end end @@ -75,9 +152,9 @@ describe QA::Factory::Base do stub_const('Some::MyDependency', dependency) allow(subject).to receive(:new).and_return(instance) + allow(subject).to receive(:current_url).and_return(product_location) allow(instance).to receive(:mydep).and_return(nil) - allow(QA::Factory::Product).to receive(:new) - allow(QA::Factory::Product).to receive(:populate!) + expect(QA::Factory::Product).to receive(:populate!) end it 'builds all dependencies first' do @@ -89,44 +166,22 @@ describe QA::Factory::Base do end describe '.product' do + include_context 'fabrication context' + subject do Class.new(described_class) do def fabricate! "any" end - # Defined only to be stubbed - def self.find_page - end - - product :token do - find_page.do_something_on_page! - 'resulting value' - end + product :token end end it 'appends new product attribute' do expect(subject.attributes).to be_one - expect(subject.attributes).to have_key(:token) - end - - describe 'populating fabrication product with data' do - let(:page) { spy('page') } - - before do - allow(factory).to receive(:class).and_return(subject) - allow(QA::Factory::Product).to receive(:new).and_return(product) - allow(product).to receive(:page).and_return(page) - allow(subject).to receive(:find_page).and_return(page) - end - - it 'populates product after fabrication' do - subject.fabricate! - - expect(product.token).to eq 'resulting value' - expect(page).to have_received(:do_something_on_page!) - end + expect(subject.attributes[0]).to be_a(QA::Factory::Product::Attribute) + expect(subject.attributes[0].name).to eq(:token) end end end diff --git a/qa/spec/factory/dependency_spec.rb b/qa/spec/factory/dependency_spec.rb index 8aaa6665a18..657beddffb1 100644 --- a/qa/spec/factory/dependency_spec.rb +++ b/qa/spec/factory/dependency_spec.rb @@ -4,11 +4,11 @@ describe QA::Factory::Dependency do let(:block) { spy('block') } let(:signature) do - double('signature', factory: dependency, block: block) + double('signature', name: :mydep, factory: dependency, block: block) end subject do - described_class.new(:mydep, factory, signature) + described_class.new(factory, signature) end describe '#overridden?' do @@ -55,16 +55,23 @@ describe QA::Factory::Dependency do expect(factory).to have_received(:mydep=).with(dependency) end - context 'when receives a caller factory as block argument' do - let(:dependency) { QA::Factory::Base } + it 'calls given block with dependency factory and caller factory' do + expect(dependency).to receive(:fabricate!).and_yield(dependency) - it 'calls given block with dependency factory and caller factory' do - allow_any_instance_of(QA::Factory::Base).to receive(:fabricate!).and_return(factory) - allow(QA::Factory::Product).to receive(:populate!).and_return(spy('any')) + subject.build! + + expect(block).to have_received(:call).with(dependency, factory) + end + + context 'with no block given' do + let(:signature) do + double('signature', name: :mydep, factory: dependency, block: nil) + end + it 'does not error' do subject.build! - expect(block).to have_received(:call).with(an_instance_of(QA::Factory::Base), factory) + expect(dependency).to have_received(:fabricate!) end end end diff --git a/qa/spec/factory/product_spec.rb b/qa/spec/factory/product_spec.rb index f245aabbf43..43b1d93d769 100644 --- a/qa/spec/factory/product_spec.rb +++ b/qa/spec/factory/product_spec.rb @@ -1,36 +1,78 @@ describe QA::Factory::Product do let(:factory) do - QA::Factory::Base.new - end - - let(:attributes) do - { test: QA::Factory::Product::Attribute.new(:test, proc { 'returned' }) } + Class.new(QA::Factory::Base) do + def foo + 'bar' + end + end.new end let(:product) { spy('product') } + let(:product_location) { 'http://product_location' } - before do - allow(QA::Factory::Base).to receive(:attributes).and_return(attributes) - end + subject { described_class.new(factory, product_location) } describe '.populate!' do - it 'returns a fabrication product and define factory attributes as its methods' do - expect(described_class).to receive(:new).and_return(product) + before do + expect(factory.class).to receive(:attributes).and_return(attributes) + end + + context 'when the product attribute is populated via a block' do + let(:attributes) do + [QA::Factory::Product::Attribute.new(:test, proc { 'returned' })] + end + + it 'returns a fabrication product and defines factory attributes as its methods' do + result = described_class.populate!(factory, product_location) + + expect(result).to be_a(described_class) + expect(result.test).to eq('returned') + end + end + + context 'when the product attribute is populated via the api' do + let(:attributes) do + [QA::Factory::Product::Attribute.new(:test)] + end - result = described_class.populate!(factory) do |instance| - instance.something = 'string' + it 'returns a fabrication product and defines factory attributes as its methods' do + expect(factory).to receive(:api_resource).and_return({ test: 'returned' }) + + result = described_class.populate!(factory, product_location) + + expect(result).to be_a(described_class) + expect(result.test).to eq('returned') end + end - expect(result).to be product - expect(result.test).to eq('returned') + context 'when the product attribute is populated via a factory attribute' do + let(:attributes) do + [QA::Factory::Product::Attribute.new(:foo)] + end + + it 'returns a fabrication product and defines factory attributes as its methods' do + result = described_class.populate!(factory, product_location) + + expect(result).to be_a(described_class) + expect(result.foo).to eq('bar') + end + end + + context 'when the product attribute has no value' do + let(:attributes) do + [QA::Factory::Product::Attribute.new(:bar)] + end + + it 'returns a fabrication product and defines factory attributes as its methods' do + expect { described_class.populate!(factory, product_location) } + .to raise_error(described_class::NoValueError, "No value was computed for product bar of factory #{factory.class.name}.") + end end end describe '.visit!' do it 'makes it possible to visit fabrication product' do allow_any_instance_of(described_class) - .to receive(:current_url).and_return('some url') - allow_any_instance_of(described_class) .to receive(:visit).and_return('visited some url') expect(subject.visit!).to eq 'visited some url' |