Commit 507fe190 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch '51826-single-product-defintion-qa' into 'master'

Always use `attribute` to define the product

Closes gitlab-qa#235 and #51826

See merge request gitlab-org/gitlab-ce!22327
parents bf96ec85 51518019
...@@ -39,7 +39,6 @@ module QA ...@@ -39,7 +39,6 @@ module QA
module Factory module Factory
autoload :ApiFabricator, 'qa/factory/api_fabricator' autoload :ApiFabricator, 'qa/factory/api_fabricator'
autoload :Base, 'qa/factory/base' autoload :Base, 'qa/factory/base'
autoload :Dependency, 'qa/factory/dependency'
autoload :Product, 'qa/factory/product' autoload :Product, 'qa/factory/product'
module Resource module Resource
......
This diff is collapsed.
...@@ -10,13 +10,42 @@ module QA ...@@ -10,13 +10,42 @@ module QA
include ApiFabricator include ApiFabricator
extend Capybara::DSL extend Capybara::DSL
def_delegators :evaluator, :dependency, :dependencies NoValueError = Class.new(RuntimeError)
def_delegators :evaluator, :product, :attributes
def_delegators :evaluator, :attribute
def fabricate!(*_args) def fabricate!(*_args)
raise NotImplementedError raise NotImplementedError
end end
def visit!
visit(web_url)
end
private
def populate_attribute(name, block)
value = attribute_value(name, block)
raise NoValueError, "No value was computed for product #{name} of factory #{self.class.name}." unless value
value
end
def attribute_value(name, block)
api_value = api_resource&.dig(name)
if api_value && block
log_having_both_api_result_and_block(name, api_value)
end
api_value || (block && instance_exec(&block))
end
def log_having_both_api_result_and_block(name, api_value)
QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored."
end
def self.fabricate!(*args, &prepare_block) def self.fabricate!(*args, &prepare_block)
fabricate_via_api!(*args, &prepare_block) fabricate_via_api!(*args, &prepare_block)
rescue NotImplementedError rescue NotImplementedError
...@@ -52,13 +81,10 @@ module QA ...@@ -52,13 +81,10 @@ module QA
def self.do_fabricate!(factory:, prepare_block:, parents: []) def self.do_fabricate!(factory:, prepare_block:, parents: [])
prepare_block.call(factory) if prepare_block prepare_block.call(factory) if prepare_block
dependencies.each do |signature|
Factory::Dependency.new(factory, signature).build!(parents: parents + [self])
end
resource_web_url = yield resource_web_url = yield
factory.web_url = resource_web_url
Factory::Product.populate!(factory, resource_web_url) Factory::Product.new(factory)
end end
private_class_method :do_fabricate! private_class_method :do_fabricate!
...@@ -85,31 +111,40 @@ module QA ...@@ -85,31 +111,40 @@ module QA
end end
private_class_method :evaluator private_class_method :evaluator
class DSL def self.dynamic_attributes
attr_reader :dependencies, :attributes const_get(:DynamicAttributes)
rescue NameError
mod = const_set(:DynamicAttributes, Module.new)
include mod
mod
end
def self.attributes_names
dynamic_attributes.instance_methods(false).sort.grep_v(/=$/)
end
class DSL
def initialize(base) def initialize(base)
@base = base @base = base
@dependencies = []
@attributes = []
end end
def dependency(factory, as:, &block) def attribute(name, &block)
as.tap do |name| @base.dynamic_attributes.module_eval do
@base.class_eval { attr_accessor name } attr_writer(name)
Dependency::Signature.new(name, factory, block).tap do |signature| define_method(name) do
@dependencies << signature instance_variable_get("@#{name}") ||
instance_variable_set(
"@#{name}",
populate_attribute(name, block))
end end
end end
end end
def product(attribute, &block)
Product::Attribute.new(attribute, block).tap do |signature|
@attributes << signature
end
end
end end
attribute :web_url
end end
end end
end end
module QA
module Factory
class Dependency
Signature = Struct.new(:name, :factory, :block)
def initialize(caller_factory, dependency_signature)
@caller_factory = caller_factory
@dependency_signature = dependency_signature
end
def overridden?
!!@caller_factory.public_send(@dependency_signature.name)
end
def build!(parents: [])
return if overridden?
dependency = @dependency_signature.factory.fabricate!(parents: parents) do |factory|
@dependency_signature.block&.call(factory, @caller_factory)
end
dependency.tap do |dependency|
@caller_factory.public_send("#{@dependency_signature.name}=", dependency)
end
end
end
end
end
...@@ -5,45 +5,30 @@ module QA ...@@ -5,45 +5,30 @@ module QA
class Product class Product
include Capybara::DSL include Capybara::DSL
NoValueError = Class.new(RuntimeError) attr_reader :factory
attr_reader :factory, :web_url def initialize(factory)
Attribute = Struct.new(:name, :block)
def initialize(factory, web_url)
@factory = factory @factory = factory
@web_url = web_url
populate_attributes! define_attributes
end end
def visit! def visit!
visit(web_url) visit(web_url)
end end
def self.populate!(factory, web_url) def populate(*attributes)
new(factory, web_url) attributes.each(&method(:public_send))
end end
private private
def populate_attributes! def define_attributes
factory.class.attributes.each do |attribute| factory.class.attributes_names.each do |name|
instance_exec(factory, attribute.block) do |factory, block| define_singleton_method(name) do
value = attribute_value(attribute, block) factory.public_send(name)
raise NoValueError, "No value was computed for product #{attribute.name} of factory #{factory.class.name}." unless value
define_singleton_method(attribute.name) { value }
end
end end
end end
def attribute_value(attribute, block)
factory.api_resource&.dig(attribute.name) ||
(block && block.call(factory)) ||
(factory.respond_to?(attribute.name) && factory.public_send(attribute.name))
end end
end end
end end
......
...@@ -2,13 +2,14 @@ module QA ...@@ -2,13 +2,14 @@ module QA
module Factory module Factory
module Repository module Repository
class ProjectPush < Factory::Repository::Push class ProjectPush < Factory::Repository::Push
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-with-code' Factory::Resource::Project.fabricate! do |resource|
project.description = 'Project with repository' resource.name = 'project-with-code'
resource.description = 'Project with repository'
end
end end
product :output attribute :output
product :project
def initialize def initialize
@file_name = 'file.txt' @file_name = 'file.txt'
......
...@@ -2,10 +2,12 @@ module QA ...@@ -2,10 +2,12 @@ module QA
module Factory module Factory
module Repository module Repository
class WikiPush < Factory::Repository::Push class WikiPush < Factory::Repository::Push
dependency Factory::Resource::Wiki, as: :wiki do |wiki| attribute :wiki do
wiki.title = 'Home' Factory::Resource::Wiki.fabricate! do |resource|
wiki.content = '# My First Wiki Content' resource.title = 'Home'
wiki.message = 'Update home' resource.content = '# My First Wiki Content'
resource.message = 'Update home'
end
end end
def initialize def initialize
......
...@@ -5,8 +5,10 @@ module QA ...@@ -5,8 +5,10 @@ module QA
attr_accessor :project, :branch_name, attr_accessor :project, :branch_name,
:allow_to_push, :allow_to_merge, :protected :allow_to_push, :allow_to_merge, :protected
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'protected-branch-project' Factory::Resource::Project.fabricate! do |resource|
resource.name = 'protected-branch-project'
end
end end
def initialize def initialize
...@@ -43,9 +45,7 @@ module QA ...@@ -43,9 +45,7 @@ module QA
# to `allow_to_push` variable. # to `allow_to_push` variable.
return branch unless @protected return branch unless @protected
Page::Project::Menu.act do Page::Project::Menu.perform(&:click_repository_settings)
click_repository_settings
end
Page::Project::Settings::Repository.perform do |setting| Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page| setting.expand_protected_branches do |page|
......
...@@ -4,11 +4,11 @@ module QA ...@@ -4,11 +4,11 @@ module QA
class DeployKey < Factory::Base class DeployKey < Factory::Base
attr_accessor :title, :key attr_accessor :title, :key
product :fingerprint do |resource| attribute :fingerprint do
Page::Project::Settings::Repository.act do Page::Project::Settings::Repository.perform do |setting|
expand_deploy_keys do |key| setting.expand_deploy_keys do |key|
key_offset = key.key_titles.index do |title| key_offset = key.key_titles.index do |key_title|
title.text == resource.title key_title.text == title
end end
key.key_fingerprints[key_offset].text key.key_fingerprints[key_offset].text
...@@ -16,17 +16,17 @@ module QA ...@@ -16,17 +16,17 @@ module QA
end end
end end
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-to-deploy' Factory::Resource::Project.fabricate! do |resource|
project.description = 'project for adding deploy key test' resource.name = 'project-to-deploy'
resource.description = 'project for adding deploy key test'
end
end end
def fabricate! def fabricate!
project.visit! project.visit!
Page::Project::Menu.act do Page::Project::Menu.perform(&:click_repository_settings)
click_repository_settings
end
Page::Project::Settings::Repository.perform do |setting| Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |page| setting.expand_deploy_keys do |page|
......
...@@ -4,25 +4,27 @@ module QA ...@@ -4,25 +4,27 @@ module QA
class DeployToken < Factory::Base class DeployToken < Factory::Base
attr_accessor :name, :expires_at attr_accessor :name, :expires_at
product :username do |resource| attribute :username do
Page::Project::Settings::Repository.act do Page::Project::Settings::Repository.perform do |page|
expand_deploy_tokens do |token| page.expand_deploy_tokens do |token|
token.token_username token.token_username
end end
end end
end end
product :password do |password| attribute :password do
Page::Project::Settings::Repository.act do Page::Project::Settings::Repository.perform do |page|
expand_deploy_tokens do |token| page.expand_deploy_tokens do |token|
token.token_password token.token_password
end end
end end
end end
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-to-deploy' Factory::Resource::Project.fabricate! do |resource|
project.description = 'project for adding deploy token test' resource.name = 'project-to-deploy'
resource.description = 'project for adding deploy token test'
end
end end
def fabricate! def fabricate!
......
...@@ -8,8 +8,10 @@ module QA ...@@ -8,8 +8,10 @@ module QA
:content, :content,
:commit_message :commit_message
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-with-new-file' Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-new-file'
end
end end
def initialize def initialize
...@@ -21,7 +23,7 @@ module QA ...@@ -21,7 +23,7 @@ module QA
def fabricate! def fabricate!
project.visit! project.visit!
Page::Project::Show.act { create_new_file! } Page::Project::Show.perform(&:create_new_file!)
Page::File::Form.perform do |page| Page::File::Form.perform do |page|
page.add_name(@name) page.add_name(@name)
......
...@@ -2,16 +2,18 @@ module QA ...@@ -2,16 +2,18 @@ module QA
module Factory module Factory
module Resource module Resource
class Fork < Factory::Base class Fork < Factory::Base
dependency Factory::Repository::ProjectPush, as: :push attribute :push do
Factory::Repository::ProjectPush.fabricate!
end
dependency Factory::Resource::User, as: :user do |user| attribute :user do
Factory::Resource::User.fabricate! do |resource|
if Runtime::Env.forker? if Runtime::Env.forker?
user.username = Runtime::Env.forker_username resource.username = Runtime::Env.forker_username
user.password = Runtime::Env.forker_password resource.password = Runtime::Env.forker_password
end
end end
end end
product :user
def visit_project_with_retry def visit_project_with_retry
# The user intermittently fails to stay signed in after visiting the # The user intermittently fails to stay signed in after visiting the
...@@ -48,15 +50,20 @@ module QA ...@@ -48,15 +50,20 @@ module QA
end end
def fabricate! def fabricate!
push
user
visit_project_with_retry visit_project_with_retry
Page::Project::Show.act { fork_project } Page::Project::Show.perform(&:fork_project)
Page::Project::Fork::New.perform do |fork_new| Page::Project::Fork::New.perform do |fork_new|
fork_new.choose_namespace(user.name) fork_new.choose_namespace(user.name)
end end
Page::Layout::Banner.act { has_notice?('The project was successfully forked.') } Page::Layout::Banner.perform do |page|
page.has_notice?('The project was successfully forked.')
end
end end
end end
end end
......
...@@ -4,12 +4,12 @@ module QA ...@@ -4,12 +4,12 @@ module QA
class Group < Factory::Base class Group < Factory::Base
attr_accessor :path, :description attr_accessor :path, :description
dependency Factory::Resource::Sandbox, as: :sandbox attribute :sandbox do
Factory::Resource::Sandbox.fabricate!
product :id do
true # We don't retrieve the Group ID when using the Browser UI
end end
attribute :id
def initialize def initialize
@path = Runtime::Namespace.name @path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}" @description = "QA test run at #{Runtime::Namespace.time}"
......
...@@ -2,22 +2,21 @@ module QA ...@@ -2,22 +2,21 @@ module QA
module Factory module Factory
module Resource module Resource
class Issue < Factory::Base class Issue < Factory::Base
attr_accessor :title, :description, :project attr_writer :description
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-for-issues' Factory::Resource::Project.fabricate! do |resource|
project.description = 'project for adding issues' resource.name = 'project-for-issues'
resource.description = 'project for adding issues'
end
end end
product :project attribute :title
product :title
def fabricate! def fabricate!
project.visit! project.visit!
Page::Project::Show.act do Page::Project::Show.perform(&:go_to_new_issue)
go_to_new_issue
end
Page::Project::Issue::New.perform do |page| Page::Project::Issue::New.perform do |page|
page.add_title(@title) page.add_title(@title)
......
...@@ -7,24 +7,21 @@ module QA ...@@ -7,24 +7,21 @@ module QA
attr_writer :project, :cluster, attr_writer :project, :cluster,
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
product :ingress_ip do attribute :ingress_ip do
Page::Project::Operations::Kubernetes::Show.perform do |page| Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
page.ingress_ip
end
end end
def fabricate! def fabricate!
@project.visit! @project.visit!
Page::Project::Menu.act { click_operations_kubernetes } Page::Project::Menu.perform(
&:click_operations_kubernetes)
Page::Project::Operations::Kubernetes::Index.perform do |page| Page::Project::Operations::Kubernetes::Index.perform(
page.add_kubernetes_cluster &:add_kubernetes_cluster)
end
Page::Project::Operations::Kubernetes::Add.perform do |page| Page::Project::Operations::Kubernetes::Add.perform(
page.add_existing_cluster &:add_existing_cluster)
end
Page::Project::Operations::Kubernetes::AddExisting.perform do |page| Page::Project::Operations::Kubernetes::AddExisting.perform do |page|
page.set_cluster_name(@cluster.cluster_name) page.set_cluster_name(@cluster.cluster_name)
......
...@@ -4,14 +4,14 @@ module QA ...@@ -4,14 +4,14 @@ module QA
module Factory module Factory
module Resource module Resource
class Label < Factory::Base class Label < Factory::Base
attr_accessor :title, attr_accessor :description, :color
:description,
:color
product(:title) { |factory| factory.title } attribute :title
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-with-label' Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-label'
end
end end
def initialize def initialize
...@@ -23,8 +23,8 @@ module QA ...@@ -23,8 +23,8 @@ module QA
def fabricate! def fabricate!
project.visit! project.visit!
Page::Project::Menu.act { go_to_labels } Page::Project::Menu.perform(&:go_to_labels)
Page::Label::Index.act { go_to_new_label } Page::Label::Index.perform(&:go_to_new_label)
Page::Label::New.perform do |page| Page::Label::New.perform do |page|
page.fill_title(@title) page.fill_title(@title)
......
...@@ -12,27 +12,33 @@ module QA ...@@ -12,27 +12,33 @@ module QA
:milestone, :milestone,
:labels :labels
product :project attribute :source_branch
product :source_branch
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-with-merge-request' Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-merge-request'
end end
end
attribute :target do
project.visit!
dependency Factory::Repository::ProjectPush, as: :target do |push, factory| Factory::Repository::ProjectPush.fabricate! do |resource|
factory.project.visit! resource.project = project
push.project = factory.project resource.branch_name = 'master'
push.branch_name = 'master' resource.remote_branch = target_branch
push.remote_branch = factory.target_branch end
end end
dependency Factory::Repository::ProjectPush, as: :source do |push, factory| attribute :source do
push.project = factory.project Factory::Repository::ProjectPush.fabricate! do |resource|
push.branch_name = factory.target_branch resource.project = project
push.remote_branch = factory.source_branch resource.branch_name = target_branch
push.new_branch = false resource.remote_branch = source_branch
push.file_name = "added_file.txt" resource.new_branch = false
push.file_content = "File Added" resource.file_name = "added_file.txt"
resource.file_content = "File Added"
end
end end
def initialize def initialize
...@@ -46,8 +52,10 @@ module QA ...@@ -46,8 +52,10 @@ module QA
end end
def fabricate! def fabricate!
target
source
project.visit! project.visit!
Page::Project::Show.act { new_merge_request } Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform do |page| Page::MergeRequest::New.perform do |page|
page.fill_title(@title) page.fill_title(@title)
page.fill_description(@description) page.fill_description(@description)
......
...@@ -4,19 +4,24 @@ module QA ...@@ -4,19 +4,24 @@ module QA
class MergeRequestFromFork < MergeRequest class MergeRequestFromFork < MergeRequest
attr_accessor :fork_branch attr_accessor :fork_branch
dependency Factory::Resource::Fork, as: :fork attribute :fork do
Factory::Resource::Fork.fabricate!
end
dependency Factory::Repository::ProjectPush, as: :push do |push, factory| attribute :push do
push.project = factory.fork Factory::Repository::ProjectPush.fabricate! do |resource|
push.branch_name = factory.fork_branch resource.project = fork
push.file_name = 'file2.txt' resource.branch_name = fork_branch
push.user = factory.fork.user resource.file_name = 'file2.txt'
resource.user = fork.user
end
end end
def fabricate! def fabricate!
push
fork.visit! fork.visit!
Page::Project::Show.act { new_merge_request } Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.act { create_merge_request } Page::MergeRequest::New.perform(&:create_merge_request)
end end
end end
end end
......
...@@ -7,13 +7,13 @@ module QA ...@@ -7,13 +7,13 @@ module QA
class PersonalAccessToken < Factory::Base class PersonalAccessToken < Factory::Base
attr_accessor :name attr_accessor :name
product :access_token do attribute :access_token do
Page::Profile::PersonalAccessTokens.act { created_access_token } Page::Profile::PersonalAccessTokens.perform(&:created_access_token)
end end
def fabricate! def fabricate!
Page::Main::Menu.act { go_to_profile_settings } Page::Main::Menu.perform(&:go_to_profile_settings)
Page::Profile::Menu.act { click_access_tokens } Page::Profile::Menu.perform(&:click_access_tokens)
Page::Profile::PersonalAccessTokens.perform do |page| Page::Profile::PersonalAccessTokens.perform do |page|
page.fill_token_name(name || 'api-test-token') page.fill_token_name(name || 'api-test-token')
......
...@@ -4,25 +4,24 @@ module QA ...@@ -4,25 +4,24 @@ module QA
module Factory module Factory
module Resource module Resource
class Project < Factory::Base class Project < Factory::Base
attr_accessor :description attribute :name
attr_reader :name attribute :description
dependency Factory::Resource::Group, as: :group attribute :group do
Factory::Resource::Group.fabricate!
product :group end
product :name
product :repository_ssh_location do attribute :repository_ssh_location do
Page::Project::Show.act do Page::Project::Show.perform do |page|
choose_repository_clone_ssh page.choose_repository_clone_ssh
repository_location page.repository_location
end end
end end
product :repository_http_location do attribute :repository_http_location do
Page::Project::Show.act do Page::Project::Show.perform do |page|
choose_repository_clone_http page.choose_repository_clone_http
repository_location page.repository_location
end end
end end
...@@ -37,7 +36,7 @@ module QA ...@@ -37,7 +36,7 @@ module QA
def fabricate! def fabricate!
group.visit! group.visit!
Page::Group::Show.act { go_to_new_project } Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page| Page::Project::New.perform do |page|
page.choose_test_namespace page.choose_test_namespace
......
...@@ -6,14 +6,16 @@ module QA ...@@ -6,14 +6,16 @@ module QA
class ProjectImportedFromGithub < Resource::Project class ProjectImportedFromGithub < Resource::Project
attr_writer :personal_access_token, :github_repository_path attr_writer :personal_access_token, :github_repository_path
dependency Factory::Resource::Group, as: :group attribute :group do
Factory::Resource::Group.fabricate!
end
product :name attribute :name
def fabricate! def fabricate!
group.visit! group.visit!
Page::Group::Show.act { go_to_new_project } Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page| Page::Project::New.perform do |page|
page.go_to_import_project page.go_to_import_project
......
...@@ -3,11 +3,12 @@ module QA ...@@ -3,11 +3,12 @@ module QA
module Resource module Resource
class ProjectMilestone < Factory::Base class ProjectMilestone < Factory::Base
attr_accessor :description attr_accessor :description
attr_reader :title
dependency Factory::Resource::Project, as: :project attribute :project do
Factory::Resource::Project.fabricate!
end
product :title attribute :title
def title=(title) def title=(title)
@title = "#{title}-#{SecureRandom.hex(4)}" @title = "#{title}-#{SecureRandom.hex(4)}"
...@@ -17,12 +18,12 @@ module QA ...@@ -17,12 +18,12 @@ module QA
def fabricate! def fabricate!
project.visit! project.visit!
Page::Project::Menu.act do Page::Project::Menu.perform do |page|
click_issues page.click_issues
click_milestones page.click_milestones
end end
Page::Project::Milestone::Index.act { click_new_milestone } Page::Project::Milestone::Index.perform(&:click_new_milestone)
Page::Project::Milestone::New.perform do |milestone_new| Page::Project::Milestone::New.perform do |milestone_new|
milestone_new.set_title(@title) milestone_new.set_title(@title)
......
...@@ -6,9 +6,11 @@ module QA ...@@ -6,9 +6,11 @@ module QA
class Runner < Factory::Base class Runner < Factory::Base
attr_writer :name, :tags, :image attr_writer :name, :tags, :image
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-with-ci-cd' Factory::Resource::Project.fabricate! do |resource|
project.description = 'Project with CI/CD Pipelines' resource.name = 'project-with-ci-cd'
resource.description = 'Project with CI/CD Pipelines'
end
end end
def name def name
...@@ -26,7 +28,7 @@ module QA ...@@ -26,7 +28,7 @@ module QA
def fabricate! def fabricate!
project.visit! project.visit!
Page::Project::Menu.act { click_ci_cd_settings } Page::Project::Menu.perform(&:click_ci_cd_settings)
Service::Runner.new(name).tap do |runner| Service::Runner.new(name).tap do |runner|
Page::Project::Settings::CICD.perform do |settings| Page::Project::Settings::CICD.perform do |settings|
......
...@@ -8,17 +8,15 @@ module QA ...@@ -8,17 +8,15 @@ module QA
class Sandbox < Factory::Base class Sandbox < Factory::Base
attr_reader :path attr_reader :path
product :id do attribute :id
true # We don't retrieve the Group ID when using the Browser UI attribute :path
end
product :path
def initialize def initialize
@path = Runtime::Namespace.sandbox_name @path = Runtime::Namespace.sandbox_name
end end
def fabricate! def fabricate!
Page::Main::Menu.act { go_to_groups } Page::Main::Menu.perform(&:go_to_groups)
Page::Dashboard::Groups.perform do |page| Page::Dashboard::Groups.perform do |page|
if page.has_group?(path) if page.has_group?(path)
......
...@@ -4,15 +4,17 @@ module QA ...@@ -4,15 +4,17 @@ module QA
class SecretVariable < Factory::Base class SecretVariable < Factory::Base
attr_accessor :key, :value attr_accessor :key, :value
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-with-secret-variables' Factory::Resource::Project.fabricate! do |resource|
project.description = 'project for adding secret variable test' resource.name = 'project-with-secret-variables'
resource.description = 'project for adding secret variable test'
end
end end
def fabricate! def fabricate!
project.visit! project.visit!
Page::Project::Menu.act { click_ci_cd_settings } Page::Project::Menu.perform(&:click_ci_cd_settings)
Page::Project::Settings::CICD.perform do |setting| Page::Project::Settings::CICD.perform do |setting|
setting.expand_secret_variables do |page| setting.expand_secret_variables do |page|
......
...@@ -6,21 +6,19 @@ module QA ...@@ -6,21 +6,19 @@ module QA
class SSHKey < Factory::Base class SSHKey < Factory::Base
extend Forwardable extend Forwardable
attr_accessor :title
attr_reader :private_key, :public_key, :fingerprint
def_delegators :key, :private_key, :public_key, :fingerprint def_delegators :key, :private_key, :public_key, :fingerprint
product :private_key attribute :private_key
product :title attribute :title
product :fingerprint attribute :fingerprint
def key def key
@key ||= Runtime::Key::RSA.new @key ||= Runtime::Key::RSA.new
end end
def fabricate! def fabricate!
Page::Main::Menu.act { go_to_profile_settings } Page::Main::Menu.perform(&:go_to_profile_settings)
Page::Profile::Menu.act { click_ssh_keys } Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |page| Page::Profile::SSHKeys.perform do |page|
page.add_key(public_key, title) page.add_key(public_key, title)
......
...@@ -5,7 +5,6 @@ module QA ...@@ -5,7 +5,6 @@ module QA
module Resource module Resource
class User < Factory::Base class User < Factory::Base
attr_reader :unique_id attr_reader :unique_id
attr_writer :username, :password, :name, :email
def initialize def initialize
@unique_id = SecureRandom.hex(8) @unique_id = SecureRandom.hex(8)
...@@ -31,14 +30,14 @@ module QA ...@@ -31,14 +30,14 @@ module QA
defined?(@username) && defined?(@password) defined?(@username) && defined?(@password)
end end
product :name attribute :name
product :username attribute :username
product :email attribute :email
product :password attribute :password
def fabricate! def fabricate!
# Don't try to log-out if we're not logged-in # Don't try to log-out if we're not logged-in
if Page::Main::Menu.act { has_personal_area?(wait: 0) } if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
Page::Main::Menu.perform { |main| main.sign_out } Page::Main::Menu.perform { |main| main.sign_out }
end end
......
...@@ -4,9 +4,11 @@ module QA ...@@ -4,9 +4,11 @@ module QA
class Wiki < Factory::Base class Wiki < Factory::Base
attr_accessor :title, :content, :message attr_accessor :title, :content, :message
dependency Factory::Resource::Project, as: :project do |project| attribute :project do
project.name = 'project-for-wikis' Factory::Resource::Project.fabricate! do |resource|
project.description = 'project for adding wikis' resource.name = 'project-for-wikis'
resource.description = 'project for adding wikis'
end
end end
def fabricate! def fabricate!
......
...@@ -5,9 +5,9 @@ module QA ...@@ -5,9 +5,9 @@ module QA
def fabricate!(*traits) def fabricate!(*traits)
raise ArgumentError unless traits.include?(:enabled) raise ArgumentError unless traits.include?(:enabled)
Page::Main::Login.act { sign_in_using_credentials } Page::Main::Login.perform(&:sign_in_using_credentials)
Page::Main::Menu.act { go_to_admin_area } Page::Main::Menu.perform(&:go_to_admin_area)
Page::Admin::Menu.act { go_to_repository_settings } Page::Admin::Menu.perform(&:go_to_repository_settings)
Page::Admin::Settings::Repository.perform do |setting| Page::Admin::Settings::Repository.perform do |setting|
setting.expand_repository_storage do |page| setting.expand_repository_storage do |page|
...@@ -16,7 +16,7 @@ module QA ...@@ -16,7 +16,7 @@ module QA
end end
end end
QA::Page::Main::Menu.act { sign_out } QA::Page::Main::Menu.perform(&:sign_out)
end end
end end
end end
......
...@@ -7,7 +7,7 @@ module QA ...@@ -7,7 +7,7 @@ module QA
module Logger module Logger
extend SingleForwardable extend SingleForwardable
def_delegators :logger, :debug, :info, :error, :warn, :fatal, :unknown def_delegators :logger, :debug, :info, :warn, :error, :fatal, :unknown
singleton_class.module_eval do singleton_class.module_eval do
def logger def logger
......
...@@ -49,11 +49,13 @@ module QA ...@@ -49,11 +49,13 @@ module QA
cluster.install_prometheus = true cluster.install_prometheus = true
cluster.install_runner = true cluster.install_runner = true
end end
kubernetes_cluster.populate(:ingress_ip)
project.visit! project.visit!
Page::Project::Menu.act { click_ci_cd_settings } Page::Project::Menu.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p| Page::Project::Settings::CICD.perform do |p|
p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io") p.enable_auto_devops_with_domain(
"#{kubernetes_cluster.ingress_ip}.nip.io")
end end
project.visit! project.visit!
......
...@@ -19,7 +19,7 @@ describe QA::Factory::Base do ...@@ -19,7 +19,7 @@ describe QA::Factory::Base do
before do before do
allow(subject).to receive(:current_url).and_return(product_location) allow(subject).to receive(:current_url).and_return(product_location)
allow(subject).to receive(:new).and_return(factory) allow(subject).to receive(:new).and_return(factory)
allow(QA::Factory::Product).to receive(:populate!).with(factory, product_location).and_return(product) allow(QA::Factory::Product).to receive(:new).with(factory).and_return(product)
end end
end end
...@@ -115,73 +115,134 @@ describe QA::Factory::Base do ...@@ -115,73 +115,134 @@ describe QA::Factory::Base do
end end
end end
describe '.dependency' do shared_context 'simple factory' do
let(:dependency) { spy('dependency') } subject do
Class.new(QA::Factory::Base) do
attribute :test do
'block'
end
before do attribute :no_block
stub_const('Some::MyDependency', dependency)
def fabricate!
'any'
end end
subject do def self.current_url
Class.new(described_class) do 'http://stub'
dependency Some::MyDependency, as: :mydep do |factory|
factory.something!
end end
end end
end end
it 'appends a new dependency and accessors' do let(:factory) { subject.new }
expect(subject.dependencies).to be_one
end end
it 'defines dependency accessors' do describe '.attribute' do
expect(subject.new).to respond_to :mydep, :mydep= include_context 'simple factory'
it 'appends new product attribute' do
expect(subject.attributes_names).to eq([:no_block, :test, :web_url])
end end
describe 'dependencies fabrication' do context 'when the product attribute is populated via a block' do
let(:dependency) { double('dependency') } it 'returns a fabrication product and defines factory attributes as its methods' do
let(:instance) { spy('instance') } result = subject.fabricate!(factory: factory)
subject do expect(result).to be_a(QA::Factory::Product)
Class.new(described_class) do expect(result.test).to eq('block')
dependency Some::MyDependency, as: :mydep
end end
end end
context 'when the product attribute is populated via the api' do
let(:api_resource) { { no_block: 'api' } }
before do before do
stub_const('Some::MyDependency', dependency) expect(factory).to receive(:api_resource).and_return(api_resource)
end
allow(subject).to receive(:new).and_return(instance) it 'returns a fabrication product and defines factory attributes as its methods' do
allow(subject).to receive(:current_url).and_return(product_location) result = subject.fabricate!(factory: factory)
allow(instance).to receive(:mydep).and_return(nil)
expect(QA::Factory::Product).to receive(:populate!) expect(result).to be_a(QA::Factory::Product)
expect(result.no_block).to eq('api')
end
context 'when the attribute also has a block in the factory' do
let(:api_resource) { { test: 'api_with_block' } }
before do
allow(QA::Runtime::Logger).to receive(:info)
end end
it 'builds all dependencies first' do it 'returns the api value and emits an INFO log entry' do
expect(dependency).to receive(:fabricate!).once result = subject.fabricate!(factory: factory)
subject.fabricate! expect(result).to be_a(QA::Factory::Product)
expect(result.test).to eq('api_with_block')
expect(QA::Runtime::Logger)
.to have_received(:info).with(/api_with_block/)
end end
end end
end end
describe '.product' do context 'when the product attribute is populated via a factory attribute' do
include_context 'fabrication context' before do
factory.test = 'value'
end
subject do it 'returns a fabrication product and defines factory attributes as its methods' do
Class.new(described_class) do result = subject.fabricate!(factory: factory)
def fabricate!
"any" expect(result).to be_a(QA::Factory::Product)
expect(result.test).to eq('value')
end
context 'when the api also has such response' do
before do
allow(factory).to receive(:api_resource).and_return({ test: 'api' })
end end
product :token it 'returns the factory attribute for the product' do
result = subject.fabricate!(factory: factory)
expect(result).to be_a(QA::Factory::Product)
expect(result.test).to eq('value')
end
end end
end end
it 'appends new product attribute' do context 'when the product attribute has no value' do
expect(subject.attributes).to be_one it 'raises an error because no values could be found' do
expect(subject.attributes[0]).to be_a(QA::Factory::Product::Attribute) result = subject.fabricate!(factory: factory)
expect(subject.attributes[0].name).to eq(:token)
expect { result.no_block }
.to raise_error(described_class::NoValueError, "No value was computed for product no_block of factory #{factory.class.name}.")
end
end
end
describe '#web_url' do
include_context 'simple factory'
it 'sets #web_url to #current_url after fabrication' do
subject.fabricate!(factory: factory)
expect(factory.web_url).to eq(subject.current_url)
end
end
describe '#visit!' do
include_context 'simple factory'
before do
allow(factory).to receive(:visit)
end
it 'calls #visit with the underlying #web_url' do
factory.web_url = subject.current_url
factory.visit!
expect(factory).to have_received(:visit).with(subject.current_url)
end end
end end
end end
describe QA::Factory::Dependency do
let(:dependency) { spy('dependency' ) }
let(:factory) { spy('factory') }
let(:block) { spy('block') }
let(:signature) do
double('signature', name: :mydep, factory: dependency, block: block)
end
subject do
described_class.new(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
it 'calls given block with dependency factory and caller factory' do
expect(dependency).to receive(:fabricate!).and_yield(dependency)
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(dependency).to have_received(:fabricate!)
end
end
end
end
end
describe QA::Factory::Product do describe QA::Factory::Product do
let(:factory) do let(:factory) do
Class.new(QA::Factory::Base) do Class.new(QA::Factory::Base) do
def foo attribute :test do
'bar' 'block'
end end
attribute :no_block
end.new end.new
end end
let(:product) { spy('product') } let(:product) { spy('product') }
let(:product_location) { 'http://product_location' } let(:product_location) { 'http://product_location' }
subject { described_class.new(factory, product_location) } subject { described_class.new(factory) }
describe '.populate!' do
before do before do
expect(factory.class).to receive(:attributes).and_return(attributes) factory.web_url = product_location
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
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
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 end
describe '.visit!' do describe '.visit!' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment