Add support for defining explicit dependencies to QA factories
This commit is contained in:
parent
88f41bf61f
commit
6df36c9d90
13 changed files with 327 additions and 50 deletions
2
qa/qa.rb
2
qa/qa.rb
|
@ -17,6 +17,8 @@ module QA
|
|||
#
|
||||
module Factory
|
||||
autoload :Base, 'qa/factory/base'
|
||||
autoload :Dependency, 'qa/factory/dependency'
|
||||
autoload :Product, 'qa/factory/product'
|
||||
|
||||
module Resource
|
||||
autoload :Sandbox, 'qa/factory/resource/sandbox'
|
||||
|
|
|
@ -1,15 +1,34 @@
|
|||
module QA
|
||||
module Factory
|
||||
class Base
|
||||
def fabricate!(*_args)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def self.fabricate!(*args)
|
||||
new.tap do |factory|
|
||||
Factory::Product.populate!(new) do |factory|
|
||||
yield factory if block_given?
|
||||
return factory.fabricate!(*args)
|
||||
|
||||
dependencies.each do |name, signature|
|
||||
Factory::Dependency.new(name, factory, signature).build!
|
||||
end
|
||||
|
||||
factory.fabricate!(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def fabricate!(*_args)
|
||||
raise NotImplementedError
|
||||
def self.dependencies
|
||||
@dependencies ||= {}
|
||||
end
|
||||
|
||||
def self.dependency(factory, as:, &block)
|
||||
as.tap do |name|
|
||||
class_eval { attr_accessor name }
|
||||
|
||||
Dependency::Signature.new(factory, block).tap do |signature|
|
||||
dependencies.store(name, signature)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
38
qa/qa/factory/dependency.rb
Normal file
38
qa/qa/factory/dependency.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
module QA
|
||||
module Factory
|
||||
class Dependency
|
||||
Signature = Struct.new(:factory, :block)
|
||||
|
||||
def initialize(name, factory, signature)
|
||||
@name = name
|
||||
@factory = factory
|
||||
@signature = signature
|
||||
end
|
||||
|
||||
def overridden?
|
||||
!!@factory.public_send(@name)
|
||||
end
|
||||
|
||||
def build!
|
||||
return if overridden?
|
||||
|
||||
Builder.new(@signature).fabricate!.tap do |product|
|
||||
@factory.public_send("#{@name}=", product)
|
||||
end
|
||||
end
|
||||
|
||||
class Builder
|
||||
def initialize(signature)
|
||||
@factory = signature.factory
|
||||
@block = signature.block
|
||||
end
|
||||
|
||||
def fabricate!
|
||||
@factory.fabricate! do |factory|
|
||||
@block&.call(factory)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
qa/qa/factory/product.rb
Normal file
26
qa/qa/factory/product.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
require 'capybara/dsl'
|
||||
|
||||
module QA
|
||||
module Factory
|
||||
class Product
|
||||
include Capybara::DSL
|
||||
|
||||
def initialize(factory)
|
||||
@factory = factory
|
||||
@location = current_url
|
||||
end
|
||||
|
||||
def visit!
|
||||
visit @location
|
||||
end
|
||||
|
||||
def self.populate!(factory)
|
||||
raise ArgumentError unless block_given?
|
||||
|
||||
yield factory
|
||||
|
||||
new(factory)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,16 +1,13 @@
|
|||
require "pry-byebug"
|
||||
|
||||
module QA
|
||||
module Factory
|
||||
module Repository
|
||||
class Push < Factory::Base
|
||||
PAGE_REGEX_CHECK =
|
||||
%r{\/#{Runtime::Namespace.sandbox_name}\/qa-test[^\/]+\/{1}[^\/]+\z}.freeze
|
||||
attr_writer :file_name, :file_content, :commit_message, :branch_name
|
||||
|
||||
attr_writer :file_name,
|
||||
:file_content,
|
||||
:commit_message,
|
||||
:branch_name
|
||||
dependency Factory::Resource::Project, as: :project do |project|
|
||||
project.name = 'project-with-code'
|
||||
project.description = 'Project with repository'
|
||||
end
|
||||
|
||||
def initialize
|
||||
@file_name = 'file.txt'
|
||||
|
@ -20,12 +17,10 @@ module QA
|
|||
end
|
||||
|
||||
def fabricate!
|
||||
project.visit!
|
||||
|
||||
Git::Repository.perform do |repository|
|
||||
repository.location = Page::Project::Show.act do
|
||||
unless PAGE_REGEX_CHECK.match(current_path)
|
||||
raise "To perform this scenario the current page should be project show."
|
||||
end
|
||||
|
||||
choose_repository_clone_http
|
||||
repository_location
|
||||
end
|
||||
|
|
|
@ -4,17 +4,29 @@ module QA
|
|||
class Group < Factory::Base
|
||||
attr_writer :path, :description
|
||||
|
||||
dependency Factory::Resource::Sandbox, as: :sandbox
|
||||
|
||||
def initialize
|
||||
@path = Runtime::Namespace.name
|
||||
@description = "QA test run at #{Runtime::Namespace.time}"
|
||||
end
|
||||
|
||||
def fabricate!
|
||||
Page::Group::New.perform do |group|
|
||||
group.set_path(@path)
|
||||
group.set_description(@description)
|
||||
group.set_visibility('Private')
|
||||
group.create
|
||||
sandbox.visit!
|
||||
|
||||
Page::Group::Show.perform do |page|
|
||||
if page.has_subgroup?(@path)
|
||||
page.go_to_subgroup(@path)
|
||||
else
|
||||
page.go_to_new_subgroup
|
||||
|
||||
Page::Group::New.perform do |group|
|
||||
group.set_path(@path)
|
||||
group.set_description(@description)
|
||||
group.set_visibility('Private')
|
||||
group.create
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,26 +6,17 @@ module QA
|
|||
class Project < Factory::Base
|
||||
attr_writer :description
|
||||
|
||||
dependency Factory::Resource::Group, as: :group
|
||||
|
||||
def name=(name)
|
||||
@name = "#{name}-#{SecureRandom.hex(8)}"
|
||||
@description = 'My awesome project'
|
||||
end
|
||||
|
||||
def fabricate!
|
||||
Factory::Resource::Sandbox.fabricate!
|
||||
group.visit!
|
||||
|
||||
Page::Group::Show.perform do |page|
|
||||
if page.has_subgroup?(Runtime::Namespace.name)
|
||||
page.go_to_subgroup(Runtime::Namespace.name)
|
||||
else
|
||||
page.go_to_new_subgroup
|
||||
|
||||
Factory::Resource::Group.fabricate! do |group|
|
||||
group.path = Runtime::Namespace.name
|
||||
end
|
||||
end
|
||||
|
||||
page.go_to_new_project
|
||||
end
|
||||
Page::Group::Show.act { go_to_new_project }
|
||||
|
||||
Page::Project::New.perform do |page|
|
||||
page.choose_test_namespace
|
||||
|
|
|
@ -6,18 +6,24 @@ module QA
|
|||
# creating it if it doesn't yet exist.
|
||||
#
|
||||
class Sandbox < Factory::Base
|
||||
def initialize
|
||||
@name = Runtime::Namespace.sandbox_name
|
||||
end
|
||||
|
||||
def fabricate!
|
||||
Page::Main::Menu.act { go_to_groups }
|
||||
|
||||
Page::Dashboard::Groups.perform do |page|
|
||||
if page.has_group?(Runtime::Namespace.sandbox_name)
|
||||
page.go_to_group(Runtime::Namespace.sandbox_name)
|
||||
if page.has_group?(@name)
|
||||
page.go_to_group(@name)
|
||||
else
|
||||
page.go_to_new_group
|
||||
|
||||
Resource::Group.fabricate! do |group|
|
||||
group.path = Runtime::Namespace.sandbox_name
|
||||
group.description = 'GitLab QA Sandbox'
|
||||
Page::Group::New.perform do |group|
|
||||
group.set_path(@name)
|
||||
group.set_description('GitLab QA Sandbox')
|
||||
group.set_visibility('Private')
|
||||
group.create
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,7 @@ module QA
|
|||
find('.dropdown-toggle').click
|
||||
find("li[data-value='new-subgroup']").click
|
||||
end
|
||||
|
||||
find("input[data-action='new-subgroup']").click
|
||||
end
|
||||
|
||||
|
@ -29,6 +30,7 @@ module QA
|
|||
find('.dropdown-toggle').click
|
||||
find("li[data-value='new-project']").click
|
||||
end
|
||||
|
||||
find("input[data-action='new-project']").click
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,15 +5,10 @@ module QA
|
|||
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
||||
Page::Main::Login.act { sign_in_using_credentials }
|
||||
|
||||
Factory::Resource::Project.fabricate! do |scenario|
|
||||
scenario.name = 'project_with_code'
|
||||
scenario.description = 'project with repository'
|
||||
end
|
||||
|
||||
Factory::Repository::Push.fabricate! do |scenario|
|
||||
scenario.file_name = 'README.md'
|
||||
scenario.file_content = '# This is test project'
|
||||
scenario.commit_message = 'Add README.md'
|
||||
Factory::Repository::Push.fabricate! do |push|
|
||||
push.file_name = 'README.md'
|
||||
push.file_content = '# This is a test project'
|
||||
push.commit_message = 'Add README.md'
|
||||
end
|
||||
|
||||
Page::Project::Show.act do
|
||||
|
@ -22,7 +17,7 @@ module QA
|
|||
end
|
||||
|
||||
expect(page).to have_content('README.md')
|
||||
expect(page).to have_content('This is test project')
|
||||
expect(page).to have_content('This is a test project')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
88
qa/spec/factory/base_spec.rb
Normal file
88
qa/spec/factory/base_spec.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
describe QA::Factory::Base do
|
||||
describe '.fabricate!' do
|
||||
subject { Class.new(described_class) }
|
||||
let(:factory) { spy('factory') }
|
||||
let(:product) { spy('product') }
|
||||
|
||||
before do
|
||||
allow(QA::Factory::Product).to receive(:new).and_return(product)
|
||||
end
|
||||
|
||||
it 'instantiates the factory and calls factory method' do
|
||||
expect(subject).to receive(:new).and_return(factory)
|
||||
|
||||
subject.fabricate!('something')
|
||||
|
||||
expect(factory).to have_received(:fabricate!).with('something')
|
||||
end
|
||||
|
||||
it 'returns fabrication product' do
|
||||
allow(subject).to receive(:new).and_return(factory)
|
||||
allow(factory).to receive(:fabricate!).and_return('something')
|
||||
|
||||
result = subject.fabricate!('something')
|
||||
|
||||
expect(result).to eq product
|
||||
end
|
||||
|
||||
it 'yields factory before calling factory method' do
|
||||
allow(subject).to receive(:new).and_return(factory)
|
||||
|
||||
subject.fabricate! do |factory|
|
||||
factory.something!
|
||||
end
|
||||
|
||||
expect(factory).to have_received(:something!).ordered
|
||||
expect(factory).to have_received(:fabricate!).ordered
|
||||
end
|
||||
end
|
||||
|
||||
describe '.dependency' do
|
||||
let(:dependency) { spy('dependency') }
|
||||
|
||||
before do
|
||||
stub_const('Some::MyDependency', dependency)
|
||||
end
|
||||
|
||||
subject do
|
||||
Class.new(described_class) do
|
||||
dependency Some::MyDependency, as: :mydep do |factory|
|
||||
factory.something!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'appends a new dependency and accessors' do
|
||||
expect(subject.dependencies).to be_one
|
||||
end
|
||||
|
||||
it 'defines dependency accessors' do
|
||||
expect(subject.new).to respond_to :mydep, :mydep=
|
||||
end
|
||||
end
|
||||
|
||||
describe 'building dependencies' do
|
||||
let(:dependency) { double('dependency') }
|
||||
let(:instance) { spy('instance') }
|
||||
|
||||
subject do
|
||||
Class.new(described_class) do
|
||||
dependency Some::MyDependency, as: :mydep
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_const('Some::MyDependency', dependency)
|
||||
|
||||
allow(subject).to receive(:new).and_return(instance)
|
||||
allow(instance).to receive(:mydep).and_return(nil)
|
||||
allow(QA::Factory::Product).to receive(:new)
|
||||
end
|
||||
|
||||
it 'builds all dependencies first' do
|
||||
expect(dependency).to receive(:fabricate!).once
|
||||
|
||||
subject.fabricate!
|
||||
end
|
||||
end
|
||||
end
|
59
qa/spec/factory/dependency_spec.rb
Normal file
59
qa/spec/factory/dependency_spec.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
describe QA::Factory::Dependency do
|
||||
let(:dependency) { spy('dependency' ) }
|
||||
let(:factory) { spy('factory') }
|
||||
let(:block) { spy('block') }
|
||||
|
||||
let(:signature) do
|
||||
double('signature', factory: dependency, block: block)
|
||||
end
|
||||
|
||||
subject do
|
||||
described_class.new(:mydep, factory, signature)
|
||||
end
|
||||
|
||||
describe '#overridden?' do
|
||||
it 'returns true if factory has overridden dependency' do
|
||||
allow(factory).to receive(:mydep).and_return('something')
|
||||
|
||||
expect(subject).to be_overridden
|
||||
end
|
||||
|
||||
it 'returns false if dependency has not been overridden' do
|
||||
allow(factory).to receive(:mydep).and_return(nil)
|
||||
|
||||
expect(subject).not_to be_overridden
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build!' do
|
||||
context 'when dependency has been overridden' do
|
||||
before do
|
||||
allow(subject).to receive(:overridden?).and_return(true)
|
||||
end
|
||||
|
||||
it 'does not fabricate dependency' do
|
||||
subject.build!
|
||||
|
||||
expect(dependency).not_to have_received(:fabricate!)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dependency has not been overridden' do
|
||||
before do
|
||||
allow(subject).to receive(:overridden?).and_return(false)
|
||||
end
|
||||
|
||||
it 'fabricates dependency' do
|
||||
subject.build!
|
||||
|
||||
expect(dependency).to have_received(:fabricate!)
|
||||
end
|
||||
|
||||
it 'sets product in the factory' do
|
||||
subject.build!
|
||||
|
||||
expect(factory).to have_received(:mydep=).with(dependency)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
44
qa/spec/factory/product_spec.rb
Normal file
44
qa/spec/factory/product_spec.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
describe QA::Factory::Product do
|
||||
let(:factory) { spy('factory') }
|
||||
let(:product) { spy('product') }
|
||||
|
||||
describe '.populate!' do
|
||||
it 'instantiates and yields factory' do
|
||||
expect(described_class).to receive(:new).with(factory)
|
||||
|
||||
described_class.populate!(factory) do |instance|
|
||||
instance.something = 'string'
|
||||
end
|
||||
|
||||
expect(factory).to have_received(:something=).with('string')
|
||||
end
|
||||
|
||||
it 'returns a fabrication product' do
|
||||
expect(described_class).to receive(:new)
|
||||
.with(factory).and_return(product)
|
||||
|
||||
result = described_class.populate!(factory) do |instance|
|
||||
instance.something = 'string'
|
||||
end
|
||||
|
||||
expect(result).to be product
|
||||
end
|
||||
|
||||
it 'raises unless block given' do
|
||||
expect { described_class.populate!(factory) }
|
||||
.to raise_error ArgumentError
|
||||
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(described_class.new(factory).visit!)
|
||||
.to eq 'visited some url'
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue