Commit 51518019 authored by Lin Jen-Shin's avatar Lin Jen-Shin Committed by Douglas Barbosa Alexandre

Always use `attribute` to define the product

parent bf96ec85
......@@ -39,7 +39,6 @@ module QA
module Factory
autoload :ApiFabricator, 'qa/factory/api_fabricator'
autoload :Base, 'qa/factory/base'
autoload :Dependency, 'qa/factory/dependency'
autoload :Product, 'qa/factory/product'
module Resource
......
This diff is collapsed.
......@@ -10,13 +10,42 @@ module QA
include ApiFabricator
extend Capybara::DSL
def_delegators :evaluator, :dependency, :dependencies
def_delegators :evaluator, :product, :attributes
NoValueError = Class.new(RuntimeError)
def_delegators :evaluator, :attribute
def fabricate!(*_args)
raise NotImplementedError
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)
fabricate_via_api!(*args, &prepare_block)
rescue NotImplementedError
......@@ -52,13 +81,10 @@ module QA
def self.do_fabricate!(factory:, prepare_block:, parents: [])
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
factory.web_url = resource_web_url
Factory::Product.populate!(factory, resource_web_url)
Factory::Product.new(factory)
end
private_class_method :do_fabricate!
......@@ -85,31 +111,40 @@ module QA
end
private_class_method :evaluator
class DSL
attr_reader :dependencies, :attributes
def self.dynamic_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)
@base = base
@dependencies = []
@attributes = []
end
def dependency(factory, as:, &block)
as.tap do |name|
@base.class_eval { attr_accessor name }
def attribute(name, &block)
@base.dynamic_attributes.module_eval do
attr_writer(name)
Dependency::Signature.new(name, factory, block).tap do |signature|
@dependencies << signature
define_method(name) do
instance_variable_get("@#{name}") ||
instance_variable_set(
"@#{name}",
populate_attribute(name, block))
end
end
end
def product(attribute, &block)
Product::Attribute.new(attribute, block).tap do |signature|
@attributes << signature
end
end
end
attribute :web_url
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,46 +5,31 @@ module QA
class Product
include Capybara::DSL
NoValueError = Class.new(RuntimeError)
attr_reader :factory
attr_reader :factory, :web_url
Attribute = Struct.new(:name, :block)
def initialize(factory, web_url)
def initialize(factory)
@factory = factory
@web_url = web_url
populate_attributes!
define_attributes
end
def visit!
visit(web_url)
end
def self.populate!(factory, web_url)
new(factory, web_url)
def populate(*attributes)
attributes.each(&method(:public_send))
end
private
def populate_attributes!
factory.class.attributes.each do |attribute|
instance_exec(factory, attribute.block) do |factory, block|
value = attribute_value(attribute, block)
raise NoValueError, "No value was computed for product #{attribute.name} of factory #{factory.class.name}." unless value
define_singleton_method(attribute.name) { value }
def define_attributes
factory.class.attributes_names.each do |name|
define_singleton_method(name) do
factory.public_send(name)
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
......@@ -2,13 +2,14 @@ module QA
module Factory
module Repository
class ProjectPush < Factory::Repository::Push
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-code'
project.description = 'Project with repository'
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-code'
resource.description = 'Project with repository'
end
end
product :output
product :project
attribute :output
def initialize
@file_name = 'file.txt'
......
......@@ -2,10 +2,12 @@ module QA
module Factory
module Repository
class WikiPush < Factory::Repository::Push
dependency Factory::Resource::Wiki, as: :wiki do |wiki|
wiki.title = 'Home'
wiki.content = '# My First Wiki Content'
wiki.message = 'Update home'
attribute :wiki do
Factory::Resource::Wiki.fabricate! do |resource|
resource.title = 'Home'
resource.content = '# My First Wiki Content'
resource.message = 'Update home'
end
end
def initialize
......
......@@ -5,8 +5,10 @@ module QA
attr_accessor :project, :branch_name,
:allow_to_push, :allow_to_merge, :protected
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'protected-branch-project'
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'protected-branch-project'
end
end
def initialize
......@@ -43,9 +45,7 @@ module QA
# to `allow_to_push` variable.
return branch unless @protected
Page::Project::Menu.act do
click_repository_settings
end
Page::Project::Menu.perform(&:click_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page|
......
......@@ -4,11 +4,11 @@ module QA
class DeployKey < Factory::Base
attr_accessor :title, :key
product :fingerprint do |resource|
Page::Project::Settings::Repository.act do
expand_deploy_keys do |key|
key_offset = key.key_titles.index do |title|
title.text == resource.title
attribute :fingerprint do
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |key|
key_offset = key.key_titles.index do |key_title|
key_title.text == title
end
key.key_fingerprints[key_offset].text
......@@ -16,17 +16,17 @@ module QA
end
end
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-to-deploy'
project.description = 'project for adding deploy key test'
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-to-deploy'
resource.description = 'project for adding deploy key test'
end
end
def fabricate!
project.visit!
Page::Project::Menu.act do
click_repository_settings
end
Page::Project::Menu.perform(&:click_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |page|
......
......@@ -4,25 +4,27 @@ module QA
class DeployToken < Factory::Base
attr_accessor :name, :expires_at
product :username do |resource|
Page::Project::Settings::Repository.act do
expand_deploy_tokens do |token|
attribute :username do
Page::Project::Settings::Repository.perform do |page|
page.expand_deploy_tokens do |token|
token.token_username
end
end
end
product :password do |password|
Page::Project::Settings::Repository.act do
expand_deploy_tokens do |token|
attribute :password do
Page::Project::Settings::Repository.perform do |page|
page.expand_deploy_tokens do |token|
token.token_password
end
end
end
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-to-deploy'
project.description = 'project for adding deploy token test'
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-to-deploy'
resource.description = 'project for adding deploy token test'
end
end
def fabricate!
......
......@@ -8,8 +8,10 @@ module QA
:content,
:commit_message
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-new-file'
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-new-file'
end
end
def initialize
......@@ -21,7 +23,7 @@ module QA
def fabricate!
project.visit!
Page::Project::Show.act { create_new_file! }
Page::Project::Show.perform(&:create_new_file!)
Page::File::Form.perform do |page|
page.add_name(@name)
......
......@@ -2,17 +2,19 @@ module QA
module Factory
module Resource
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|
if Runtime::Env.forker?
user.username = Runtime::Env.forker_username
user.password = Runtime::Env.forker_password
attribute :user do
Factory::Resource::User.fabricate! do |resource|
if Runtime::Env.forker?
resource.username = Runtime::Env.forker_username
resource.password = Runtime::Env.forker_password
end
end
end
product :user
def visit_project_with_retry
# The user intermittently fails to stay signed in after visiting the
# project page. The new user is registered and then signs in and a
......@@ -48,15 +50,20 @@ module QA
end
def fabricate!
push
user
visit_project_with_retry
Page::Project::Show.act { fork_project }
Page::Project::Show.perform(&:fork_project)
Page::Project::Fork::New.perform do |fork_new|
fork_new.choose_namespace(user.name)
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
......
......@@ -4,12 +4,12 @@ module QA
class Group < Factory::Base
attr_accessor :path, :description
dependency Factory::Resource::Sandbox, as: :sandbox
product :id do
true # We don't retrieve the Group ID when using the Browser UI
attribute :sandbox do
Factory::Resource::Sandbox.fabricate!
end
attribute :id
def initialize
@path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}"
......
......@@ -2,22 +2,21 @@ module QA
module Factory
module Resource
class Issue < Factory::Base
attr_accessor :title, :description, :project
attr_writer :description
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-for-issues'
project.description = 'project for adding issues'
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-for-issues'
resource.description = 'project for adding issues'
end
end
product :project
product :title
attribute :title
def fabricate!
project.visit!
Page::Project::Show.act do
go_to_new_issue
end
Page::Project::Show.perform(&:go_to_new_issue)
Page::Project::Issue::New.perform do |page|
page.add_title(@title)
......
......@@ -7,24 +7,21 @@ module QA
attr_writer :project, :cluster,
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
product :ingress_ip do
Page::Project::Operations::Kubernetes::Show.perform do |page|
page.ingress_ip
end
attribute :ingress_ip do
Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
end
def fabricate!
@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.add_kubernetes_cluster
end
Page::Project::Operations::Kubernetes::Index.perform(
&:add_kubernetes_cluster)
Page::Project::Operations::Kubernetes::Add.perform do |page|
page.add_existing_cluster
end
Page::Project::Operations::Kubernetes::Add.perform(
&:add_existing_cluster)
Page::Project::Operations::Kubernetes::AddExisting.perform do |page|
page.set_cluster_name(@cluster.cluster_name)
......
......@@ -4,14 +4,14 @@ module QA
module Factory
module Resource
class Label < Factory::Base
attr_accessor :title,
:description,
:color
attr_accessor :description, :color
product(:title) { |factory| factory.title }
attribute :title
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-label'
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-label'
end
end
def initialize
......@@ -23,8 +23,8 @@ module QA
def fabricate!
project.visit!
Page::Project::Menu.act { go_to_labels }
Page::Label::Index.act { go_to_new_label }
Page::Project::Menu.perform(&:go_to_labels)
Page::Label::Index.perform(&:go_to_new_label)
Page::Label::New.perform do |page|
page.fill_title(@title)
......
......@@ -12,27 +12,33 @@ module QA
:milestone,
:labels
product :project
product :source_branch
attribute :source_branch
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-merge-request'
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-merge-request'
end
end
dependency Factory::Repository::ProjectPush, as: :target do |push, factory|
factory.project.visit!
push.project = factory.project
push.branch_name = 'master'
push.remote_branch = factory.target_branch
attribute :target do
project.visit!
Factory::Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.branch_name = 'master'
resource.remote_branch = target_branch
end
end
dependency Factory::Repository::ProjectPush, as: :source do |push, factory|
push.project = factory.project
push.branch_name = factory.target_branch
push.remote_branch = factory.source_branch
push.new_branch = false
push.file_name = "added_file.txt"
push.file_content = "File Added"
attribute :source do
Factory::Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.branch_name = target_branch
resource.remote_branch = source_branch
resource.new_branch = false
resource.file_name = "added_file.txt"
resource.file_content = "File Added"
end
end
def initialize
......@@ -46,8 +52,10 @@ module QA
end
def fabricate!
target
source
project.visit!
Page::Project::Show.act { new_merge_request }
Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform do |page|
page.fill_title(@title)
page.fill_description(@description)
......
......@@ -4,19 +4,24 @@ module QA
class MergeRequestFromFork < MergeRequest
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|
push.project = factory.fork
push.branch_name = factory.fork_branch
push.file_name = 'file2.txt'
push.user = factory.fork.user
attribute :push do
Factory::Repository::ProjectPush.fabricate! do |resource|
resource.project = fork
resource.branch_name = fork_branch
resource.file_name = 'file2.txt'
resource.user = fork.user
end
end
def fabricate!
push
fork.visit!
Page::Project::Show.act { new_merge_request }
Page::MergeRequest::New.act { create_merge_request }
Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform(&:create_merge_request)
end
end
end
......
......@@ -7,13 +7,13 @@ module QA
class PersonalAccessToken < Factory::Base
attr_accessor :name
product :access_token do
Page::Profile::PersonalAccessTokens.act { created_access_token }
attribute :access_token do
Page::Profile::PersonalAccessTokens.perform(&:created_access_token)
end
def fabricate!
Page::Main::Menu.act { go_to_profile_settings }
Page::Profile::Menu.act { click_access_tokens }
Page::Main::Menu.perform(&:go_to_profile_settings)
Page::Profile::Menu.perform(&:click_access_tokens)
Page::Profile::PersonalAccessTokens.perform do |page|
page.fill_token_name(name || 'api-test-token')
......
......@@ -4,25 +4,24 @@ module QA
module Factory
module Resource
class Project < Factory::Base
attr_accessor :description
attr_reader :name
attribute :name
attribute :description
dependency Factory::Resource::Group, as: :group
product :group
product :name
attribute :group do
Factory::Resource::Group.fabricate!
end
product :repository_ssh_location do
Page::Project::Show.act do
choose_repository_clone_ssh
repository_location
attribute :repository_ssh_location do
Page::Project::Show.perform do |page|
page.choose_repository_clone_ssh
page.repository_location
end
end
product :repository_http_location do
Page::Project::Show.act do
choose_repository_clone_http
repository_location
attribute :repository_http_location do
Page::Project::Show.perform do |page|
page.choose_repository_clone_http
page.repository_location
end
end
......@@ -37,7 +36,7 @@ module QA
def fabricate!
group.visit!
Page::Group::Show.act { go_to_new_project }
Page::Group::Show.perform(&:go_to_new_project)