Commit 4d0fd75c authored by Lin Jen-Shin's avatar Lin Jen-Shin

Rename QA::Factory to QA::Resource

* Factory::Base -> Resource::Base, and therefore:
* Factory::Resource::Project -> Resource::Project
parent c12a4a9a
......@@ -36,42 +36,40 @@ module QA
##
# GitLab QA fabrication mechanisms
#
module Factory
autoload :ApiFabricator, 'qa/factory/api_fabricator'
autoload :Base, 'qa/factory/base'
module Resource
autoload :Sandbox, 'qa/factory/resource/sandbox'
autoload :Group, 'qa/factory/resource/group'
autoload :Issue, 'qa/factory/resource/issue'
autoload :Project, 'qa/factory/resource/project'
autoload :Label, 'qa/factory/resource/label'
autoload :MergeRequest, 'qa/factory/resource/merge_request'
autoload :ProjectImportedFromGithub, 'qa/factory/resource/project_imported_from_github'
autoload :MergeRequestFromFork, 'qa/factory/resource/merge_request_from_fork'
autoload :DeployKey, 'qa/factory/resource/deploy_key'
autoload :DeployToken, 'qa/factory/resource/deploy_token'
autoload :Branch, 'qa/factory/resource/branch'
autoload :CiVariable, 'qa/factory/resource/ci_variable'
autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster'
autoload :User, 'qa/factory/resource/user'
autoload :ProjectMilestone, 'qa/factory/resource/project_milestone'
autoload :Wiki, 'qa/factory/resource/wiki'
autoload :File, 'qa/factory/resource/file'
autoload :Fork, 'qa/factory/resource/fork'
autoload :SSHKey, 'qa/factory/resource/ssh_key'
end
autoload :ApiFabricator, 'qa/resource/api_fabricator'
autoload :Base, 'qa/resource/base'
autoload :Sandbox, 'qa/resource/sandbox'
autoload :Group, 'qa/resource/group'
autoload :Issue, 'qa/resource/issue'
autoload :Project, 'qa/resource/project'
autoload :Label, 'qa/resource/label'
autoload :MergeRequest, 'qa/resource/merge_request'
autoload :ProjectImportedFromGithub, 'qa/resource/project_imported_from_github'
autoload :MergeRequestFromFork, 'qa/resource/merge_request_from_fork'
autoload :DeployKey, 'qa/resource/deploy_key'
autoload :DeployToken, 'qa/resource/deploy_token'
autoload :Branch, 'qa/resource/branch'
autoload :CiVariable, 'qa/resource/ci_variable'
autoload :Runner, 'qa/resource/runner'
autoload :PersonalAccessToken, 'qa/resource/personal_access_token'
autoload :KubernetesCluster, 'qa/resource/kubernetes_cluster'
autoload :User, 'qa/resource/user'
autoload :ProjectMilestone, 'qa/resource/project_milestone'
autoload :Wiki, 'qa/resource/wiki'
autoload :File, 'qa/resource/file'
autoload :Fork, 'qa/resource/fork'
autoload :SSHKey, 'qa/resource/ssh_key'
module Repository
autoload :Push, 'qa/factory/repository/push'
autoload :ProjectPush, 'qa/factory/repository/project_push'
autoload :WikiPush, 'qa/factory/repository/wiki_push'
autoload :Push, 'qa/resource/repository/push'
autoload :ProjectPush, 'qa/resource/repository/project_push'
autoload :WikiPush, 'qa/resource/repository/wiki_push'
end
module Settings
autoload :HashedStorage, 'qa/factory/settings/hashed_storage'
autoload :HashedStorage, 'qa/resource/settings/hashed_storage'
end
end
......
module QA
module Factory
module Resource
class Branch < Factory::Base
attr_accessor :project, :branch_name,
:allow_to_push, :allow_to_merge, :protected
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'protected-branch-project'
end
end
def initialize
@branch_name = 'test/branch'
@allow_to_push = true
@allow_to_merge = true
@protected = false
end
def fabricate!
project.visit!
Factory::Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.file_name = 'kick-off.txt'
resource.commit_message = 'First commit'
end
branch = Factory::Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.file_name = 'README.md'
resource.commit_message = 'Add readme'
resource.branch_name = 'master'
resource.new_branch = false
resource.remote_branch = @branch_name
end
Page::Project::Show.perform do |page|
page.wait { page.has_content?(branch_name) }
end
# The upcoming process will make it access the Protected Branches page,
# select the already created branch and protect it according
# to `allow_to_push` variable.
return branch unless @protected
Page::Project::Menu.perform(&:click_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page|
page.select_branch(branch_name)
if allow_to_push
page.allow_devs_and_maintainers_to_push
else
page.allow_no_one_to_push
end
if allow_to_merge
page.allow_devs_and_maintainers_to_merge
else
page.allow_no_one_to_merge
end
page.wait(reload: false) do
!page.first('.btn-success').disabled?
end
page.protect_branch
end
end
end
end
end
end
end
module QA
module Factory
module Resource
class CiVariable < Factory::Base
attr_accessor :key, :value
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-ci-variables'
resource.description = 'project for adding CI variable test'
end
end
def fabricate!
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_settings)
Page::Project::Settings::CICD.perform do |setting|
setting.expand_ci_variables do |page|
page.fill_variable(key, value)
page.save_variables
end
end
end
end
end
end
end
module QA
module Factory
module Resource
class DeployKey < Factory::Base
attr_accessor :title, :key
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
end
end
end
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.perform(&:click_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |page|
page.fill_key_title(title)
page.fill_key_value(key)
page.add_key
end
end
end
end
end
end
end
module QA
module Factory
module Resource
class DeployToken < Factory::Base
attr_accessor :name, :expires_at
attribute :username do
Page::Project::Settings::Repository.perform do |page|
page.expand_deploy_tokens do |token|
token.token_username
end
end
end
attribute :password do
Page::Project::Settings::Repository.perform do |page|
page.expand_deploy_tokens do |token|
token.token_password
end
end
end
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!
project.visit!
Page::Project::Menu.act do
click_repository_settings
end
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_tokens do |page|
page.fill_token_name(name)
page.fill_token_expires_at(expires_at)
page.fill_scopes(read_repository: true, read_registry: false)
page.add_token
end
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module Factory
module Resource
class File < Factory::Base
attr_accessor :name,
:content,
:commit_message
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-new-file'
end
end
def initialize
@name = 'QA Test - File name'
@content = 'QA Test - File content'
@commit_message = 'QA Test - Commit message'
end
def fabricate!
project.visit!
Page::Project::Show.perform(&:create_new_file!)
Page::File::Form.perform do |page|
page.add_name(@name)
page.add_content(@content)
page.add_commit_message(@commit_message)
page.commit_changes
end
end
end
end
end
end
module QA
module Factory
module Resource
class Fork < Factory::Base
attribute :push do
Factory::Repository::ProjectPush.fabricate!
end
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
def fabricate!
populate(:push, :user)
# Sign out as admin and sign is as the fork user
Page::Main::Menu.perform(&:sign_out)
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform do |login|
login.sign_in_using_credentials(user)
end
push.project.visit!
Page::Project::Show.perform(&:fork_project)
Page::Project::Fork::New.perform do |fork_new|
fork_new.choose_namespace(user.name)
end
Page::Layout::Banner.perform do |page|
page.has_notice?('The project was successfully forked.')
end
end
end
end
end
end
module QA
module Factory
module Resource
class Group < Factory::Base
attr_accessor :path, :description
attribute :sandbox do
Factory::Resource::Sandbox.fabricate!
end
attribute :id
def initialize
@path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}"
end
def fabricate!
sandbox.visit!
Page::Group::Show.perform do |group_show|
if group_show.has_subgroup?(path)
group_show.go_to_subgroup(path)
else
group_show.go_to_new_subgroup
Page::Group::New.perform do |group_new|
group_new.set_path(path)
group_new.set_description(description)
group_new.set_visibility('Public')
group_new.create
end
# Ensure that the group was actually created
group_show.wait(time: 1) do
group_show.has_text?(path) &&
group_show.has_new_project_or_subgroup_dropdown?
end
end
end
end
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
super
end
def api_get_path
"/groups/#{CGI.escape("#{sandbox.path}/#{path}")}"
end
def api_post_path
'/groups'
end
def api_post_body
{
parent_id: sandbox.id,
path: path,
name: path,
visibility: 'public'
}
end
end
end
end
end
module QA
module Factory
module Resource
class Issue < Factory::Base
attr_writer :description
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-for-issues'
resource.description = 'project for adding issues'
end
end
attribute :title
def fabricate!
project.visit!
Page::Project::Show.perform(&:go_to_new_issue)
Page::Project::Issue::New.perform do |page|
page.add_title(@title)
page.add_description(@description)
page.create_new_issue
end
end
end
end
end
end
require 'securerandom'
module QA
module Factory
module Resource
class KubernetesCluster < Factory::Base
attr_writer :project, :cluster,
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
attribute :ingress_ip do
Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
end
def fabricate!
@project.visit!
Page::Project::Menu.perform(
&:click_operations_kubernetes)
Page::Project::Operations::Kubernetes::Index.perform(
&:add_kubernetes_cluster)
Page::Project::Operations::Kubernetes::Add.perform(
&:add_existing_cluster)
Page::Project::Operations::Kubernetes::AddExisting.perform do |page|
page.set_cluster_name(@cluster.cluster_name)
page.set_api_url(@cluster.api_url)
page.set_ca_certificate(@cluster.ca_certificate)
page.set_token(@cluster.token)
page.check_rbac! if @cluster.rbac
page.add_cluster!
end
if @install_helm_tiller
Page::Project::Operations::Kubernetes::Show.perform do |page|
# We must wait a few seconds for permissions to be set up correctly for new cluster
sleep 10
# Helm must be installed before everything else
page.install!(:helm)
page.await_installed(:helm)
page.install!(:ingress) if @install_ingress
page.install!(:prometheus) if @install_prometheus
page.install!(:runner) if @install_runner
page.await_installed(:ingress) if @install_ingress
page.await_installed(:prometheus) if @install_prometheus
page.await_installed(:runner) if @install_runner
end
end
end
end
end
end
end
require 'securerandom'
module QA
module Factory
module Resource
class Label < Factory::Base
attr_accessor :description, :color
attribute :title
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-label'
end
end
def initialize
@title = "qa-test-#{SecureRandom.hex(8)}"
@description = 'This is a test label'
@color = '#0033CC'
end
def fabricate!
project.visit!
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)
page.fill_description(@description)
page.fill_color(@color)
page.create_label
end
end
end
end
end
end
require 'securerandom'
module QA
module Factory
module Resource
class MergeRequest < Factory::Base
attr_accessor :title,
:description,
:source_branch,
:target_branch,
:assignee,
:milestone,
:labels
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-merge-request'
end
end
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
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
@title = 'QA test - merge request'
@description = 'This is a test merge request'
@source_branch = "qa-test-feature-#{SecureRandom.hex(8)}"
@target_branch = "master"
@assignee = nil
@milestone = nil
@labels = []
end
def fabricate!
populate(:target, :source)
project.visit!
Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform do |page|
page.fill_title(@title)
page.fill_description(@description)
page.choose_milestone(@milestone) if @milestone
labels.each do |label|
page.select_label(label)
end
page.create_merge_request
end
end
end
end
end
end
module QA
module Factory
module Resource
class MergeRequestFromFork < MergeRequest
attr_accessor :fork_branch
attribute :fork do
Factory::Resource::Fork.fabricate!
end
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!
populate(:push)
fork.visit!
Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform(&:create_merge_request)
end
end
end
end
end
module QA
module Factory
module Resource
##
# Create a personal access token that can be used by the api
#
class PersonalAccessToken < Factory::Base
attr_accessor :name
attribute :access_token do
Page::Profile::PersonalAccessTokens.perform(&:created_access_token)
end
def fabricate!
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')
page.check_api
page.create_token
end
end
end
end
end
end
require 'securerandom'
module QA
module Factory
module Resource
class Project < Factory::Base
attribute :name
attribute :description
attribute :group do
Factory::Resource::Group.fabricate!
end
attribute :repository_ssh_location do
Page::Project::Show.perform do |page|
page.choose_repository_clone_ssh
page.repository_location
end
end
attribute :repository_http_location do
Page::Project::Show.perform do |page|
page.choose_repository_clone_http
page.repository_location
end
end
def initialize
@description = 'My awesome project'
end
def name=(raw_name)
@name = "#{raw_name}-#{SecureRandom.hex(8)}"
end
def fabricate!
group.visit!
Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page|
page.choose_test_namespace
page.choose_name(@name)
page.add_description(@description)
page.set_visibility('Public')
page.create_new_project
end
end
def api_get_path
"/projects/#{name}"
end
def api_post_path
'/projects'
end
def api_post_body
{
namespace_id: group.id,
path: name,
name: name,
description: description,
visibility: 'public'
}
end
private
def transform_api_resource(resource)
resource[:repository_ssh_location] = Git::Location.new(resource[:ssh_url_to_repo])
resource[:repository_http_location] = Git::Location.new(resource[:http_url_to_repo])
resource
end
end
end
end
end
require 'securerandom'
module QA
module Factory
module Resource
class ProjectImportedFromGithub < Resource::Project
attr_accessor :name
attr_writer :personal_access_token, :github_repository_path
attribute :group do
Factory::Resource::Group.fabricate!
end
def fabricate!
group.visit!
Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page|
page.go_to_import_project
end
Page::Project::New.perform do |page|
page.go_to_github_import
end
Page::Project::Import::Github.perform do |page|
page.add_personal_access_token(@personal_access_token)
page.list_repos
page.import!(@github_repository_path, @name)
end
end
end
end
end
end
module QA
module Factory
module Resource
class ProjectMilestone < Factory::Base
attr_reader :title
attr_accessor :description
attribute :project do
Factory::Resource::Project.fabricate!
end
def title=(title)
@title = "#{title}-#{SecureRandom.hex(4)}"
@description = 'A milestone'
end
def fabricate!
project.visit!
Page::Project::Menu.perform do |page|
page.click_issues
page.click_milestones
end
Page::Project::Milestone::Index.perform(&:click_new_milestone)
Page::Project::Milestone::New.perform do |milestone_new|
milestone_new.set_title(@title)
milestone_new.set_description(@description)
milestone_new.create_new_milestone
end
end
end
end
end
end
require 'securerandom'
module QA
module Factory
module Resource
class Runner < Factory::Base
attr_writer :name, :tags, :image
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-with-ci-cd'
resource.description = 'Project with CI/CD Pipelines'
end
end
def name
@name || "qa-runner-#{SecureRandom.hex(4)}"
end
def tags
@tags || %w[qa e2e]
end
def image
@image || 'gitlab/gitlab-runner:alpine'
end
def fabricate!
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_settings)
Service::Runner.new(name).tap do |runner|
Page::Project::Settings::CICD.perform do |settings|
settings.expand_runners_settings do |runners|
runner.pull
runner.token = runners.registration_token
runner.address = runners.coordinator_address
runner.tags = tags
runner.image = image
runner.register!
end
end
end
end
end
end
end
end
module QA
module Factory
module Resource
##
# Ensure we're in our sandbox namespace, either by navigating to it or by
# creating it if it doesn't yet exist.
#
class Sandbox < Factory::Base
attr_reader :path
attribute :id
def initialize
@path = Runtime::Namespace.sandbox_name
end
def fabricate!
Page::Main::Menu.perform(&:go_to_groups)
Page::Dashboard::Groups.perform do |page|
if page.has_group?(path)
page.go_to_group(path)
else
page.go_to_new_group
Page::Group::New.perform do |group|
group.set_path(path)
group.set_description('GitLab QA Sandbox Group')
group.set_visibility('Public')
group.create
end
end
end
end
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
super
end
def api_get_path
"/groups/#{path}"
end
def api_post_path
'/groups'
end
def api_post_body
{
path: path,
name: path,
visibility: 'public'
}
end
end
end
end
end
# frozen_string_literal: true
module QA
module Factory
module Resource
class SSHKey < Factory::Base
extend Forwardable
attr_accessor :title
def_delegators :key, :private_key, :public_key, :fingerprint
def key
@key ||= Runtime::Key::RSA.new
end
def fabricate!
Page::Main::Menu.perform(&:go_to_profile_settings)
Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |page|
page.add_key(public_key, title)
end
end
end
end
end
end
require 'securerandom'
module QA
module Factory
module Resource
class User < Factory::Base
attr_reader :unique_id
attr_writer :username, :password
def initialize
@unique_id = SecureRandom.hex(8)
end
def username
@username ||= "qa-user-#{unique_id}"
end
def password
@password ||= 'password'
end
def name
@name ||= username
end
def email
@email ||= "#{username}@example.com"
end
def credentials_given?
defined?(@username) && defined?(@password)
end
def fabricate!
# Don't try to log-out if we're not logged-in
if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
Page::Main::Menu.perform { |main| main.sign_out }
end
if credentials_given?
Page::Main::Login.perform do |login|
login.sign_in_using_credentials(self)
end
else
Page::Main::Login.perform do |login|
login.switch_to_register_tab
end
Page::Main::SignUp.perform do |signup|
signup.sign_up!(self)
end
end
end
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
super
end
def api_get_path
"/users/#{fetch_id(username)}"
end
def api_post_path
'/users'
end
def api_post_body
{
email: email,
password: password,
username: username,
name: name,
skip_confirmation: true
}
end
private
def fetch_id(username)
users = parse_body(api_get_from("/users?username=#{username}"))
unless users.size == 1 && users.first[:username] == username
raise ResourceNotFoundError, "Expected one user with username #{username} but found: `#{users}`."
end
users.first[:id]
end
end
end
end
end
module QA
module Factory
module Resource
class Wiki < Factory::Base
attr_accessor :title, :content, :message
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'project-for-wikis'
resource.description = 'project for adding wikis'
end
end
def fabricate!
project.visit!
Page::Project::Menu.perform { |menu_side| menu_side.click_wiki }
Page::Project::Wiki::New.perform do |wiki_new|
wiki_new.go_to_create_first_page
wiki_new.set_title(@title)
wiki_new.set_content(@content)
wiki_new.set_message(@message)
wiki_new.create_new_page
end
end
end
end
end
end
......@@ -65,7 +65,7 @@ module QA
end
def sign_in_using_admin_credentials
admin = QA::Factory::Resource::User.new.tap do |user|
admin = QA::Resource::User.new.tap do |user|
user.username = QA::Runtime::User.admin_username
user.password = QA::Runtime::User.admin_password
end
......
# Factory objects in GitLab QA
# Resource class in GitLab QA
In GitLab QA we are using factories to create resources.
Resources are primarily created using Browser UI steps, but can also
be created via the API.
Factories implementation are primarily done using Browser UI steps, but can also
be done via the API.
## How to properly implement a resource class?
## Why do we need that?
All resource classes should inherit from [`Resource::Base`](./base.rb).
We need factory objects because we need to reduce duplication when creating
resources for our QA tests.
## How to properly implement a factory object?
All factories should inherit from [`Factory::Base`](./base.rb).
There is only one mandatory method to implement to define a factory. This is the
`#fabricate!` method, which is used to build a resource via the browser UI.
Note that you should only use [Page objects](../page/README.md) to interact with
a Web page in this method.
There is only one mandatory method to implement to define a resource class.
This is the `#fabricate!` method, which is used to build the resource via the
browser UI. Note that you should only use [Page objects](../page/README.md) to
interact with a Web page in this method.
Here is an imaginary example:
```ruby
module QA
module Factory
module Resource
class Shirt < Factory::Base
class Shirt < Base
attr_accessor :name
def fabricate!
......@@ -40,26 +32,24 @@ module QA
end
end
end
end
end
```
### Define API implementation
A factory may also implement the three following methods to be able to create a
resource via the public GitLab API:
A resource class may also implement the three following methods to be able to
create the resource via the public GitLab API:
- `#api_get_path`: The `GET` path to fetch an existing resource.
- `#api_post_path`: The `POST` path to create a new resource.
- `#api_post_body`: The `POST` body (as a Ruby hash) to create a new resource.
Let's take the `Shirt` factory example, and add these three API methods:
Let's take the `Shirt` resource class, and add these three API methods:
```ruby
module QA
module Factory
module Resource
class Shirt < Factory::Base
class Shirt < Base
attr_accessor :name
def fabricate!
......@@ -81,11 +71,10 @@ module QA
end
end
end
end
end
```
The [`Project` factory](./resource/project.rb) is a good real example of Browser
The [`Project` resource](./project.rb) is a good real example of Browser
UI and API implementations.
#### Resource attributes
......@@ -94,23 +83,22 @@ A resource may need another resource to exist first. For instance, a project
needs a group to be created in.
To define a resource attribute, you can use the `attribute` method with a
block using the other factory to fabricate the resource.
block using the other resource class to fabricate the resource.
That will allow access to the other resource from your resource object's
methods. You would usually use it in `#fabricate!`, `#api_get_path`,
`#api_post_path`, `#api_post_body`.
Let's take the `Shirt` factory, and add a `project` attribute to it:
Let's take the `Shirt` resource class, and add a `project` attribute to it:
```ruby
module QA
module Factory
module Resource
class Shirt < Factory::Base
class Shirt < Base
attr_accessor :name
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
Project.fabricate! do |resource|
resource.name = 'project-to-create-a-shirt'
end
end
......@@ -143,7 +131,6 @@ module QA
end
end
end
end
end
```
......@@ -161,17 +148,16 @@ SSH URL as an attribute.
Again we could use the `attribute` method with a block, using a page object
to retrieve the data on the page.
Let's take the `Shirt` factory, and define a `:brand` attribute:
Let's take the `Shirt` resource class, and define a `:brand` attribute:
```ruby
module QA
module Factory
module Resource
class Shirt < Factory::Base
class Shirt < Base
attr_accessor :name
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
Project.fabricate! do |resource|
resource.name = 'project-to-create-a-shirt'
end
end
......@@ -186,7 +172,6 @@ module QA
# ... same as before
end
end
end
end
```
......@@ -198,7 +183,7 @@ Consider this:
```ruby
shirt =
QA::Factory::Resource::Shirt.fabricate! do |resource|
QA::Resource::Shirt.fabricate! do |resource|
resource.name = "GitLab QA"
end
......@@ -214,7 +199,7 @@ retrieve the brand before visiting the project again:
```ruby
shirt =
QA::Factory::Resource::Shirt.fabricate! do |resource|
QA::Resource::Shirt.fabricate! do |resource|
resource.name = "GitLab QA"
end
......@@ -232,9 +217,8 @@ ending fabrication:
```ruby
module QA
module Factory
module Resource
class Shirt < Factory::Base
class Shirt < Base
# ... same as before
def fabricate!
......@@ -253,7 +237,6 @@ module QA
end
end
end
end
end
```
......@@ -262,20 +245,20 @@ attribute respectively. Here `populate(:brand)` has the same effect as
just `brand`. Using the populate method makes the intention clearer.
With this, it will make sure we construct the data right after we create the
shirt. The drawback is that this will always construct the data when the resource is fabricated even if we don't need to use the data.
shirt. The drawback is that this will always construct the data when the
resource is fabricated even if we don't need to use the data.
Alternatively, we could just make sure we're on the right page before
constructing the brand data:
```ruby
module QA
module Factory
module Resource
class Shirt < Factory::Base
class Shirt < Base
attr_accessor :name
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
Project.fabricate! do |resource|
resource.name = 'project-to-create-a-shirt'
end
end
......@@ -295,7 +278,6 @@ module QA
# ... same as before
end
end
end
end
```
......@@ -319,24 +301,24 @@ the API returns
you may want to store `style` as-is in the resource, and fetch the first value
of the first `materials` item in a `main_fabric` attribute.
Let's take the `Shirt` factory, and define a `:style` and a `:main_fabric`
attributes:
Let's take the `Shirt` resource class, and define a `:style` and a
`:main_fabric` attributes:
```ruby
module QA
module Factory
module Resource
class Shirt < Factory::Base
class Shirt < Base
# ... same as before
# Attribute from the Shirt factory if present,
# @style from the instance if present,
# or fetched from the API response if present,
# or a QA::Factory::Base::NoValueError is raised otherwise
# or a QA::Resource::Base::NoValueError is raised otherwise
attribute :style
# If the attribute from the Shirt factory is not present,
# If @main_fabric is not present,
# and if the API does not contain this field, this block will be
# used to construct the value based on the API response.
# used to construct the value based on the API response, and
# store the result in @main_fabric
attribute :main_fabric do
api_response.&dig(:materials, 0, 0)
end
......@@ -344,33 +326,32 @@ module QA
# ... same as before
end
end
end
end
```
**Notes on attributes precedence:**
- factory instance variables have the highest precedence
- resource instance variables have the highest precedence
- attributes from the API response take precedence over attributes from the
block (usually from Browser UI)
- attributes without a value will raise a `QA::Factory::Base::NoValueError` error
- attributes without a value will raise a `QA::Resource::Base::NoValueError` error
## Creating resources in your tests
To create a resource in your tests, you can call the `.fabricate!` method on the
factory class.
Note that if the factory supports API fabrication, this will use this
To create a resource in your tests, you can call the `.fabricate!` method on
the resource class.
Note that if the resource class supports API fabrication, this will use this
fabrication by default.
Here is an example that will use the API fabrication method under the hood since
it's supported by the `Shirt` factory:
Here is an example that will use the API fabrication method under the hood
since it's supported by the `Shirt` resource class:
```ruby
my_shirt = Factory::Resource::Shirt.fabricate! do |shirt|
my_shirt = Resource::Shirt.fabricate! do |shirt|
shirt.name = 'my-shirt'
end
expect(page).to have_text(my_shirt.name) # => "my-shirt" from the factory's attribute
expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable
expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response
expect(page).to have_text(my_shirt.style) # => "t-shirt" from the API response
expect(page).to have_text(my_shirt.main_fabric) # => "cotton" from the API response via the block
......@@ -380,26 +361,27 @@ If you explicitly want to use the Browser UI fabrication method, you can call
the `.fabricate_via_browser_ui!` method instead:
```ruby
my_shirt = Factory::Resource::Shirt.fabricate_via_browser_ui! do |shirt|
my_shirt = Resource::Shirt.fabricate_via_browser_ui! do |shirt|
shirt.name = 'my-shirt'
end
expect(page).to have_text(my_shirt.name) # => "my-shirt" from the factory's attribute
expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable
expect(page).to have_text(my_shirt.brand) # => the brand name fetched from the `Page::Shirt::Show` page via the block
expect(page).to have_text(my_shirt.style) # => QA::Factory::Base::NoValueError will be raised because no API response nor a block is provided
expect(page).to have_text(my_shirt.main_fabric) # => QA::Factory::Base::NoValueError will be raised because no API response and the block didn't provide a value (because it's also based on the API response)
expect(page).to have_text(my_shirt.style) # => QA::Resource::Base::NoValueError will be raised because no API response nor a block is provided
expect(page).to have_text(my_shirt.main_fabric) # => QA::Resource::Base::NoValueError will be raised because no API response and the block didn't provide a value (because it's also based on the API response)
```
You can also explicitly use the API fabrication method, by calling the
`.fabricate_via_api!` method:
```ruby
my_shirt = Factory::Resource::Shirt.fabricate_via_api! do |shirt|
my_shirt = Resource::Shirt.fabricate_via_api! do |shirt|
shirt.name = 'my-shirt'
end
```
In this case, the result will be similar to calling `Factory::Resource::Shirt.fabricate!`.
In this case, the result will be similar to calling
`Resource::Shirt.fabricate!`.
## Where to ask for help?
......
......@@ -5,7 +5,7 @@ require 'active_support/core_ext/object/deep_dup'
require 'capybara/dsl'
module QA
module Factory
module Resource
module ApiFabricator
include Airborne
include Capybara::DSL
......@@ -27,7 +27,7 @@ module QA
def fabricate_via_api!
unless api_support?
raise NotImplementedError, "Factory #{self.class.name} does not support fabrication via the API!"
raise NotImplementedError, "Resource #{self.class.name} does not support fabrication via the API!"
end
resource_web_url(api_post)
......@@ -93,8 +93,8 @@ module QA
self.api_resource = transform_api_resource(parsed_response.deep_dup)
end
def transform_api_resource(resource)
resource
def transform_api_resource(api_resource)
api_resource
end
end
end
......
......@@ -4,7 +4,7 @@ require 'forwardable'
require 'capybara/dsl'
module QA
module Factory
module Resource
class Base
extend SingleForwardable
include ApiFabricator
......@@ -58,11 +58,11 @@ module QA
def self.fabricate_via_browser_ui!(*args, &prepare_block)
options = args.extract_options!
factory = options.fetch(:factory) { new }
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
do_fabricate!(factory: factory, prepare_block: prepare_block, parents: parents) do
log_fabrication(:browser_ui, factory, parents, args) { factory.fabricate!(*args) }
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
current_url
end
......@@ -70,29 +70,29 @@ module QA
def self.fabricate_via_api!(*args, &prepare_block)
options = args.extract_options!
factory = options.fetch(:factory) { new }
resource = options.fetch(:resource) { new }
parents = options.fetch(:parents) { [] }
raise NotImplementedError unless factory.api_support?
raise NotImplementedError unless resource.api_support?
factory.eager_load_api_client!
resource.eager_load_api_client!
do_fabricate!(factory: factory, prepare_block: prepare_block, parents: parents) do
log_fabrication(:api, factory, parents, args) { factory.fabricate_via_api! }
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
end
end
def self.do_fabricate!(factory:, prepare_block:, parents: [])
prepare_block.call(factory) if prepare_block
def self.do_fabricate!(resource:, prepare_block:, parents: [])
prepare_block.call(resource) if prepare_block
resource_web_url = yield
factory.web_url = resource_web_url
resource.web_url = resource_web_url
factory
resource
end
private_class_method :do_fabricate!
def self.log_fabrication(method, factory, parents, args)
def self.log_fabrication(method, resource, parents, args)
return yield unless Runtime::Env.debug?
start = Time.now
......@@ -111,7 +111,7 @@ module QA
private_class_method :log_fabrication
def self.evaluator
@evaluator ||= Factory::Base::DSL.new(self)
@evaluator ||= Base::DSL.new(self)
end
private_class_method :evaluator
......
# frozen_string_literal: true
module QA
module Resource
class Branch < Base
attr_accessor :project, :branch_name,
:allow_to_push, :allow_to_merge, :protected
attribute :project do
Project.fabricate! do |resource|
resource.name = 'protected-branch-project'
end
end
def initialize
@branch_name = 'test/branch'
@allow_to_push = true
@allow_to_merge = true
@protected = false
end
def fabricate!
project.visit!
Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.file_name = 'kick-off.txt'
resource.commit_message = 'First commit'
end
branch = Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.file_name = 'README.md'
resource.commit_message = 'Add readme'
resource.branch_name = 'master'
resource.new_branch = false
resource.remote_branch = @branch_name
end
Page::Project::Show.perform do |page|
page.wait { page.has_content?(branch_name) }
end
# The upcoming process will make it access the Protected Branches page,
# select the already created branch and protect it according
# to `allow_to_push` variable.
return branch unless @protected
Page::Project::Menu.perform(&:click_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page|
page.select_branch(branch_name)
if allow_to_push
page.allow_devs_and_maintainers_to_push
else
page.allow_no_one_to_push
end
if allow_to_merge
page.allow_devs_and_maintainers_to_merge
else
page.allow_no_one_to_merge
end
page.wait(reload: false) do
!page.first('.btn-success').disabled?
end
page.protect_branch
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class CiVariable < Base
attr_accessor :key, :value
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-with-ci-variables'
resource.description = 'project for adding CI variable test'
end
end
def fabricate!
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_settings)
Page::Project::Settings::CICD.perform do |setting|
setting.expand_ci_variables do |page|
page.fill_variable(key, value)
page.save_variables
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class DeployKey < Base
attr_accessor :title, :key
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
end
end
end
attribute :project do
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.perform(&:click_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |page|
page.fill_key_title(title)
page.fill_key_value(key)
page.add_key
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class DeployToken < Base
attr_accessor :name, :expires_at
attribute :username do
Page::Project::Settings::Repository.perform do |page|
page.expand_deploy_tokens do |token|
token.token_username
end
end
end
attribute :password do
Page::Project::Settings::Repository.perform do |page|
page.expand_deploy_tokens do |token|
token.token_password
end
end
end
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-to-deploy'
resource.description = 'project for adding deploy token test'
end
end
def fabricate!
project.visit!
Page::Project::Menu.act do
click_repository_settings
end
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_tokens do |page|
page.fill_token_name(name)
page.fill_token_expires_at(expires_at)
page.fill_scopes(read_repository: true, read_registry: false)
page.add_token
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class File < Base
attr_accessor :name,
:content,
:commit_message
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-with-new-file'
end
end
def initialize
@name = 'QA Test - File name'
@content = 'QA Test - File content'
@commit_message = 'QA Test - Commit message'
end
def fabricate!
project.visit!
Page::Project::Show.perform(&:create_new_file!)
Page::File::Form.perform do |page|
page.add_name(@name)
page.add_content(@content)
page.add_commit_message(@commit_message)
page.commit_changes
end
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class Fork < Base
attribute :push do
Repository::ProjectPush.fabricate!
end
attribute :user do
User.fabricate! do |resource|
if Runtime::Env.forker?
resource.username = Runtime::Env.forker_username
resource.password = Runtime::Env.forker_password
end
end
end
def fabricate!
populate(:push, :user)
# Sign out as admin and sign is as the fork user
Page::Main::Menu.perform(&:sign_out)
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform do |login|
login.sign_in_using_credentials(user)
end
push.project.visit!
Page::Project::Show.perform(&:fork_project)
Page::Project::Fork::New.perform do |fork_new|
fork_new.choose_namespace(user.name)
end
Page::Layout::Banner.perform do |page|
page.has_notice?('The project was successfully forked.')
end
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class Group < Base
attr_accessor :path, :description
attribute :sandbox do
Sandbox.fabricate!
end
attribute :id
def initialize
@path = Runtime::Namespace.name
@description = "QA test run at #{Runtime::Namespace.time}"
end
def fabricate!
sandbox.visit!
Page::Group::Show.perform do |group_show|
if group_show.has_subgroup?(path)
group_show.go_to_subgroup(path)
else
group_show.go_to_new_subgroup
Page::Group::New.perform do |group_new|
group_new.set_path(path)
group_new.set_description(description)
group_new.set_visibility('Public')
group_new.create
end
# Ensure that the group was actually created
group_show.wait(time: 1) do
group_show.has_text?(path) &&
group_show.has_new_project_or_subgroup_dropdown?
end
end
end
end
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
super
end
def api_get_path
"/groups/#{CGI.escape("#{sandbox.path}/#{path}")}"
end
def api_post_path
'/groups'
end
def api_post_body
{
parent_id: sandbox.id,
path: path,
name: path,
visibility: 'public'
}
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class Issue < Base
attr_writer :description
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-for-issues'
resource.description = 'project for adding issues'
end
end
attribute :title
def fabricate!
project.visit!
Page::Project::Show.perform(&:go_to_new_issue)
Page::Project::Issue::New.perform do |page|
page.add_title(@title)
page.add_description(@description)
page.create_new_issue
end
end
end
end
end
# frozen_string_literal: true
require 'securerandom'
module QA
module Resource
class KubernetesCluster < Base
attr_writer :project, :cluster,
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
attribute :ingress_ip do
Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
end
def fabricate!
@project.visit!
Page::Project::Menu.perform(
&:click_operations_kubernetes)
Page::Project::Operations::Kubernetes::Index.perform(
&:add_kubernetes_cluster)
Page::Project::Operations::Kubernetes::Add.perform(
&:add_existing_cluster)
Page::Project::Operations::Kubernetes::AddExisting.perform do |page|
page.set_cluster_name(@cluster.cluster_name)
page.set_api_url(@cluster.api_url)
page.set_ca_certificate(@cluster.ca_certificate)
page.set_token(@cluster.token)
page.check_rbac! if @cluster.rbac
page.add_cluster!
end
if @install_helm_tiller
Page::Project::Operations::Kubernetes::Show.perform do |page|
# We must wait a few seconds for permissions to be set up correctly for new cluster
sleep 10
# Helm must be installed before everything else
page.install!(:helm)
page.await_installed(:helm)
page.install!(:ingress) if @install_ingress
page.install!(:prometheus) if @install_prometheus
page.install!(:runner) if @install_runner
page.await_installed(:ingress) if @install_ingress
page.await_installed(:prometheus) if @install_prometheus
page.await_installed(:runner) if @install_runner
end
end
end
end
end
end
# frozen_string_literal: true
require 'securerandom'
module QA
module Resource
class Label < Base
attr_accessor :description, :color
attribute :title
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-with-label'
end
end
def initialize
@title = "qa-test-#{SecureRandom.hex(8)}"
@description = 'This is a test label'
@color = '#0033CC'
end
def fabricate!
project.visit!
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)
page.fill_description(@description)
page.fill_color(@color)
page.create_label
end
end
end
end
end
# frozen_string_literal: true
require 'securerandom'
module QA
module Resource
class MergeRequest < Base
attr_accessor :title,
:description,
:source_branch,
:target_branch,
:assignee,
:milestone,
:labels
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-with-merge-request'
end
end
attribute :target do
project.visit!
Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.branch_name = 'master'
resource.remote_branch = target_branch
end
end
attribute :source do
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
@title = 'QA test - merge request'
@description = 'This is a test merge request'
@source_branch = "qa-test-feature-#{SecureRandom.hex(8)}"
@target_branch = "master"
@assignee = nil
@milestone = nil
@labels = []
end
def fabricate!
populate(:target, :source)
project.visit!
Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform do |page|
page.fill_title(@title)
page.fill_description(@description)
page.choose_milestone(@milestone) if @milestone
labels.each do |label|
page.select_label(label)
end
page.create_merge_request
end
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class MergeRequestFromFork < MergeRequest
attr_accessor :fork_branch
attribute :fork do
Fork.fabricate!
end
attribute :push do
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!
populate(:push)
fork.visit!
Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform(&:create_merge_request)
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
##
# Create a personal access token that can be used by the api
#
class PersonalAccessToken < Base
attr_accessor :name
attribute :access_token do
Page::Profile::PersonalAccessTokens.perform(&:created_access_token)
end
def fabricate!
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')
page.check_api
page.create_token
end
end
end
end
end
# frozen_string_literal: true
require 'securerandom'
module QA
module Resource
class Project < Base
attribute :name
attribute :description
attribute :group do
Group.fabricate!
end
attribute :repository_ssh_location do
Page::Project::Show.perform do |page|
page.choose_repository_clone_ssh
page.repository_location
end
end
attribute :repository_http_location do
Page::Project::Show.perform do |page|
page.choose_repository_clone_http
page.repository_location
end
end
def initialize
@description = 'My awesome project'
end
def name=(raw_name)
@name = "#{raw_name}-#{SecureRandom.hex(8)}"
end
def fabricate!
group.visit!
Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page|
page.choose_test_namespace
page.choose_name(@name)
page.add_description(@description)
page.set_visibility('Public')
page.create_new_project
end
end
def api_get_path
"/projects/#{name}"
end
def api_post_path
'/projects'
end
def api_post_body
{
namespace_id: group.id,
path: name,
name: name,
description: description,
visibility: 'public'
}
end
private
def transform_api_resource(api_resource)
api_resource[:repository_ssh_location] =
Git::Location.new(api_resource[:ssh_url_to_repo])
api_resource[:repository_http_location] =
Git::Location.new(api_resource[:http_url_to_repo])
api_resource
end
end
end
end
# frozen_string_literal: true
require 'securerandom'
module QA
module Resource
class ProjectImportedFromGithub < Project
attr_accessor :name
attr_writer :personal_access_token, :github_repository_path
attribute :group do
Group.fabricate!
end
def fabricate!
group.visit!
Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page|
page.go_to_import_project
end
Page::Project::New.perform do |page|
page.go_to_github_import
end
Page::Project::Import::Github.perform do |page|
page.add_personal_access_token(@personal_access_token)
page.list_repos
page.import!(@github_repository_path, @name)
end
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class ProjectMilestone < Base
attr_reader :title
attr_accessor :description
attribute :project do
Project.fabricate!
end
def title=(title)
@title = "#{title}-#{SecureRandom.hex(4)}"
@description = 'A milestone'
end
def fabricate!
project.visit!
Page::Project::Menu.perform do |page|
page.click_issues
page.click_milestones
end
Page::Project::Milestone::Index.perform(&:click_new_milestone)
Page::Project::Milestone::New.perform do |milestone_new|
milestone_new.set_title(@title)
milestone_new.set_description(@description)
milestone_new.create_new_milestone
end
end
end
end
end
# frozen_string_literal: true
module QA
module Factory
module Resource
module Repository
class ProjectPush < Factory::Repository::Push
class ProjectPush < Repository::Push
attribute :project do
Factory::Resource::Project.fabricate! do |resource|
Project.fabricate! do |resource|
resource.name = 'project-with-code'
resource.description = 'Project with repository'
end
......
# frozen_string_literal: true
require 'pathname'
module QA
module Factory
module Resource
module Repository
class Push < Factory::Base
class Push < Base
attr_accessor :file_name, :file_content, :commit_message,
:branch_name, :new_branch, :output, :repository_http_uri,
:repository_ssh_uri, :ssh_key, :user
......
# frozen_string_literal: true
module QA
module Factory
module Resource
module Repository
class WikiPush < Factory::Repository::Push
class WikiPush < Repository::Push
attribute :wiki do
Factory::Resource::Wiki.fabricate! do |resource|
Wiki.fabricate! do |resource|
resource.title = 'Home'
resource.content = '# My First Wiki Content'
resource.message = 'Update home'
......
# frozen_string_literal: true
require 'securerandom'
module QA
module Resource
class Runner < Base
attr_writer :name, :tags, :image
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-with-ci-cd'
resource.description = 'Project with CI/CD Pipelines'
end
end
def name
@name || "qa-runner-#{SecureRandom.hex(4)}"
end
def tags
@tags || %w[qa e2e]
end
def image
@image || 'gitlab/gitlab-runner:alpine'
end
def fabricate!
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_settings)
Service::Runner.new(name).tap do |runner|
Page::Project::Settings::CICD.perform do |settings|
settings.expand_runners_settings do |runners|
runner.pull
runner.token = runners.registration_token
runner.address = runners.coordinator_address
runner.tags = tags
runner.image = image
runner.register!
end
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
##
# Ensure we're in our sandbox namespace, either by navigating to it or by
# creating it if it doesn't yet exist.
#
class Sandbox < Base
attr_reader :path
attribute :id
def initialize
@path = Runtime::Namespace.sandbox_name
end
def fabricate!
Page::Main::Menu.perform(&:go_to_groups)
Page::Dashboard::Groups.perform do |page|
if page.has_group?(path)
page.go_to_group(path)
else
page.go_to_new_group
Page::Group::New.perform do |group|
group.set_path(path)
group.set_description('GitLab QA Sandbox Group')
group.set_visibility('Public')
group.create
end
end
end
end
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
super
end
def api_get_path
"/groups/#{path}"
end
def api_post_path
'/groups'
end
def api_post_body
{
path: path,
name: path,
visibility: 'public'
}
end
end
end
end
# frozen_string_literal: true
module QA
module Factory
module Resource
module Settings
class HashedStorage < Factory::Base
class HashedStorage < Base
def fabricate!(*traits)
raise ArgumentError unless traits.include?(:enabled)
......
# frozen_string_literal: true
module QA
module Resource
class SSHKey < Base
extend Forwardable
attr_accessor :title
def_delegators :key, :private_key, :public_key, :fingerprint
def key
@key ||= Runtime::Key::RSA.new
end
def fabricate!
Page::Main::Menu.perform(&:go_to_profile_settings)
Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |page|
page.add_key(public_key, title)
end
end
end
end
end
# frozen_string_literal: true
require 'securerandom'
module QA
module Resource
class User < Base
attr_reader :unique_id
attr_writer :username, :password
def initialize
@unique_id = SecureRandom.hex(8)
end
def username
@username ||= "qa-user-#{unique_id}"
end
def password
@password ||= 'password'
end
def name
@name ||= username
end
def email
@email ||= "#{username}@example.com"
end
def credentials_given?
defined?(@username) && defined?(@password)
end
def fabricate!
# Don't try to log-out if we're not logged-in
if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
Page::Main::Menu.perform { |main| main.sign_out }
end
if credentials_given?
Page::Main::Login.perform do |login|
login.sign_in_using_credentials(self)
end
else
Page::Main::Login.perform do |login|
login.switch_to_register_tab
end
Page::Main::SignUp.perform do |signup|
signup.sign_up!(self)
end
end
end
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
super
end
def api_get_path
"/users/#{fetch_id(username)}"
end
def api_post_path
'/users'
end
def api_post_body
{
email: email,
password: password,
username: username,
name: name,
skip_confirmation: true
}
end
private
def fetch_id(username)
users = parse_body(api_get_from("/users?username=#{username}"))
unless users.size == 1 && users.first[:username] == username
raise ResourceNotFoundError, "Expected one user with username #{username} but found: `#{users}`."
end
users.first[:id]
end
end
end
end
# frozen_string_literal: true
module QA
module Resource
class Wiki < Base
attr_accessor :title, :content, :message
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-for-wikis'
resource.description = 'project for adding wikis'
end
end
def fabricate!
project.visit!
Page::Project::Menu.perform { |menu_side| menu_side.click_wiki }
Page::Project::Wiki::New.perform do |wiki_new|
wiki_new.go_to_create_first_page
wiki_new.set_title(@title)
wiki_new.set_content(@content)
wiki_new.set_message(@message)
wiki_new.create_new_page
end
end
end
end
end
......@@ -32,7 +32,7 @@ module QA
def do_create_personal_access_token
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::PersonalAccessToken.fabricate!.access_token
Resource::PersonalAccessToken.fabricate!.access_token
end
end
end
......
......@@ -5,7 +5,7 @@ module QA
it 'user registers and logs in' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Factory::Resource::User.fabricate_via_browser_ui!
Resource::User.fabricate!
# TODO, since `Signed in successfully` message was removed
# this is the only way to tell if user is signed in correctly.
......
......@@ -7,9 +7,9 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
user = Factory::Resource::User.fabricate!
user = Resource::User.fabricate!
project = Factory::Resource::Project.fabricate! do |resource|
project = Resource::Project.fabricate! do |resource|
resource.name = 'add-member-project'
end
project.visit!
......
......@@ -7,7 +7,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
created_project = Factory::Resource::Project.fabricate_via_browser_ui! do |project|
created_project = Resource::Project.fabricate_via_browser_ui! do |project|
project.name = 'awesome-project'
project.description = 'create awesome project test'
end
......
......@@ -4,7 +4,7 @@ module QA
context 'Manage', :orchestrated, :github do
describe 'Project import from GitHub' do
let(:imported_project) do
Factory::Resource::ProjectImportedFromGithub.fabricate! do |project|
Resource::ProjectImportedFromGithub.fabricate! do |project|
project.name = 'imported-project'
project.personal_access_token = Runtime::Env.github_access_token
project.github_repository_path = 'gitlab-qa/test-project'
......
......@@ -7,7 +7,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Repository::ProjectPush.fabricate! do |push|
Resource::Repository::ProjectPush.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is a test project'
push.commit_message = 'Add README.md'
......
......@@ -9,7 +9,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Issue.fabricate! do |issue|
Resource::Issue.fabricate! do |issue|
issue.title = issue_title
end
end
......
......@@ -9,7 +9,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Issue.fabricate! do |issue|
Resource::Issue.fabricate! do |issue|
issue.title = issue_title
end
......
......@@ -7,22 +7,22 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
current_project = Factory::Resource::Project.fabricate! do |project|
current_project = Resource::Project.fabricate! do |project|
project.name = 'project-with-merge-request-and-milestone'
end
current_milestone = Factory::Resource::ProjectMilestone.fabricate! do |milestone|
current_milestone = Resource::ProjectMilestone.fabricate! do |milestone|
milestone.title = 'unique-milestone'
milestone.project = current_project
end
new_label = Factory::Resource::Label.fabricate! do |label|
new_label = Resource::Label.fabricate! do |label|
label.project = current_project
label.title = 'qa-mr-test-label'
label.description = 'Merge Request label'
end
Factory::Resource::MergeRequest.fabricate! do |merge_request|
Resource::MergeRequest.fabricate! do |merge_request|
merge_request.title = 'This is a merge request with a milestone'
merge_request.description = 'Great feature with milestone'
merge_request.project = current_project
......@@ -49,11 +49,11 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
current_project = Factory::Resource::Project.fabricate! do |project|
current_project = Resource::Project.fabricate! do |project|
project.name = 'project-with-merge-request'
end
Factory::Resource::MergeRequest.fabricate! do |merge_request|
Resource::MergeRequest.fabricate! do |merge_request|
merge_request.title = 'This is a merge request'
merge_request.description = 'Great feature'
merge_request.project = current_project
......
......@@ -7,7 +7,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
merge_request = Factory::Resource::MergeRequestFromFork.fabricate! do |merge_request|
merge_request = Resource::MergeRequestFromFork.fabricate! do |merge_request|
merge_request.fork_branch = 'feature-branch'
end
......
......@@ -7,7 +7,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project = Resource::Project.fabricate! do |project|
project.name = "only-fast-forward"
end
project.visit!
......@@ -15,12 +15,12 @@ module QA
Page::Project::Menu.act { go_to_settings }
Page::Project::Settings::MergeRequest.act { enable_ff_only }
merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request = Resource::MergeRequest.fabricate! do |merge_request|
merge_request.project = project
merge_request.title = 'Needs rebasing'
end
Factory::Repository::ProjectPush.fabricate! do |push|
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.file_name = "other.txt"
push.file_content = "New file added!"
......
......@@ -7,16 +7,16 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project = Resource::Project.fabricate! do |project|
project.name = "squash-before-merge"
end
merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request = Resource::MergeRequest.fabricate! do |merge_request|
merge_request.project = project
merge_request.title = 'Squashing commits'
end
Factory::Repository::ProjectPush.fabricate! do |push|
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.commit_message = 'to be squashed'
push.branch_name = merge_request.source_branch
......
......@@ -13,7 +13,7 @@ module QA
before(:all) do
login
@project = Factory::Resource::Project.fabricate! do |project|
@project = Resource::Project.fabricate! do |project|
project.name = 'file-template-project'
project.description = 'Add file templates via the Files view'
end
......
......@@ -9,7 +9,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
key = Factory::Resource::SSHKey.fabricate! do |resource|
key = Resource::SSHKey.fabricate! do |resource|
resource.title = key_title
end
......
......@@ -14,7 +14,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |scenario|
project = Resource::Project.fabricate! do |scenario|
scenario.name = 'project-with-code'
scenario.description = 'project for git clone tests'
end
......
......@@ -12,7 +12,7 @@ module QA
file_content = 'QA Test - File content'
commit_message_for_create = 'QA Test - Create new file'
Factory::Resource::File.fabricate! do |file|
Resource::File.fabricate! do |file|
file.name = file_name
file.content = file_content
file.commit_message = commit_message_for_create
......
......@@ -7,14 +7,14 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
access_token = Factory::Resource::PersonalAccessToken.fabricate!.access_token
access_token = Resource::PersonalAccessToken.fabricate!.access_token
user = Factory::Resource::User.new.tap do |user|
user = Resource::User.new.tap do |user|
user.username = Runtime::User.username
user.password = access_token
end
push = Factory::Repository::ProjectPush.fabricate! do |push|
push = Resource::Repository::ProjectPush.fabricate! do |push|
push.user = user
push.file_name = 'README.md'
push.file_content = '# This is a test project'
......
......@@ -7,7 +7,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Repository::ProjectPush.fabricate! do |push|
Resource::Repository::ProjectPush.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is a test project'
push.commit_message = 'Add README.md'
......
......@@ -6,7 +6,7 @@ module QA
let(:branch_name) { 'protected-branch' }
let(:commit_message) { 'Protected push commit message' }
let(:project) do
Factory::Resource::Project.fabricate! do |resource|
Resource::Project.fabricate! do |resource|
resource.name = 'protected-branch-project'
end
end
......@@ -47,7 +47,7 @@ module QA
end
def create_protected_branch(allow_to_push:)
Factory::Resource::Branch.fabricate! do |resource|
Resource::Branch.fabricate! do |resource|
resource.branch_name = branch_name
resource.project = project
resource.allow_to_push = allow_to_push
......@@ -56,7 +56,7 @@ module QA
end
def push_new_file(branch)
Factory::Repository::ProjectPush.fabricate! do |resource|
Resource::Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.file_name = 'new_file.md'
resource.file_content = '# This is a new file'
......
......@@ -12,11 +12,11 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
key = Factory::Resource::SSHKey.fabricate! do |resource|
key = Resource::SSHKey.fabricate! do |resource|
resource.title = key_title
end
Factory::Repository::ProjectPush.fabricate! do |push|
Resource::Repository::ProjectPush.fabricate! do |push|
push.ssh_key = key
push.file_name = 'README.md'
push.file_content = '# Test Use SSH Key'
......
......@@ -13,7 +13,7 @@ module QA
before(:all) do
login
@project = Factory::Resource::Project.fabricate! do |project|
@project = Resource::Project.fabricate! do |project|
project.name = 'file-template-project'
project.description = 'Add file templates via the Web IDE'
end
......
......@@ -18,7 +18,7 @@ module QA
end
it 'user creates, edits, clones, and pushes to the wiki' do
wiki = Factory::Resource::Wiki.fabricate! do |resource|
wiki = Resource::Wiki.fabricate! do |resource|
resource.title = 'Home'
resource.content = '# My First Wiki Content'
resource.message = 'Update home'
......@@ -34,7 +34,7 @@ module QA
validate_content('My Second Wiki Content')
Factory::Repository::WikiPush.fabricate! do |push|
Resource::Repository::WikiPush.fabricate! do |push|
push.wiki = wiki
push.file_name = 'Home.md'
push.file_content = '# My Third Wiki Content'
......
......@@ -7,7 +7,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::CiVariable.fabricate! do |resource|
Resource::CiVariable.fabricate! do |resource|
resource.key = 'VARIABLE_KEY'
resource.value = 'some CI variable'
end
......
......@@ -13,18 +13,18 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project = Resource::Project.fabricate! do |project|
project.name = 'project-with-pipelines'
project.description = 'Project with CI/CD Pipelines.'
end
Factory::Resource::Runner.fabricate! do |runner|
Resource::Runner.fabricate! do |runner|
runner.project = project
runner.name = executor
runner.tags = %w[qa test]
end
Factory::Repository::ProjectPush.fabricate! do |push|
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.file_name = '.gitlab-ci.yml'
push.commit_message = 'Add .gitlab-ci.yml'
......
......@@ -13,7 +13,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Runner.fabricate! do |runner|
Resource::Runner.fabricate! do |runner|
runner.name = executor
end
......
......@@ -11,7 +11,7 @@ module QA
deploy_key_title = 'deploy key title'
deploy_key_value = key.public_key
deploy_key = Factory::Resource::DeployKey.fabricate! do |resource|
deploy_key = Resource::DeployKey.fabricate! do |resource|
resource.title = deploy_key_title
resource.key = deploy_key_value
end
......
......@@ -15,13 +15,13 @@ module QA
@runner_name = "qa-runner-#{Time.now.to_i}"
@project = Factory::Resource::Project.fabricate! do |resource|
@project = Resource::Project.fabricate! do |resource|
resource.name = 'deploy-key-clone-project'
end
@repository_location = @project.repository_ssh_location
Factory::Resource::Runner.fabricate! do |resource|
Resource::Runner.fabricate! do |resource|
resource.project = @project
resource.name = @runner_name
resource.tags = %w[qa docker]
......@@ -47,7 +47,7 @@ module QA
login
Factory::Resource::DeployKey.fabricate! do |resource|
Resource::DeployKey.fabricate! do |resource|
resource.project = @project
resource.title = "deploy key #{key.name}(#{key.bits})"
resource.key = key.public_key
......@@ -55,7 +55,7 @@ module QA
deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}"
Factory::Resource::CiVariable.fabricate! do |resource|
Resource::CiVariable.fabricate! do |resource|
resource.project = @project
resource.key = deploy_key_name
resource.value = key.private_key
......@@ -78,7 +78,7 @@ module QA
- docker
YAML
Factory::Repository::ProjectPush.fabricate! do |resource|
Resource::Repository::ProjectPush.fabricate! do |resource|
resource.project = @project
resource.file_name = '.gitlab-ci.yml'
resource.commit_message = 'Add .gitlab-ci.yml'
......
......@@ -10,7 +10,7 @@ module QA
deploy_token_name = 'deploy token name'
deploy_token_expires_at = Date.today + 7 # 1 Week from now
deploy_token = Factory::Resource::DeployToken.fabricate! do |resource|
deploy_token = Resource::DeployToken.fabricate! do |resource|
resource.name = deploy_token_name
resource.expires_at = deploy_token_expires_at
end
......
......@@ -15,21 +15,21 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |p|
project = Resource::Project.fabricate! do |p|
p.name = 'project-with-autodevops'
p.description = 'Project with Auto Devops'
end
# Disable code_quality check in Auto DevOps pipeline as it takes
# too long and times out the test
Factory::Resource::CiVariable.fabricate! do |resource|
Resource::CiVariable.fabricate! do |resource|
resource.project = project
resource.key = 'CODE_QUALITY_DISABLED'
resource.value = '1'
end
# Create Auto Devops compatible repo
Factory::Repository::ProjectPush.fabricate! do |push|
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.directory = Pathname
.new(__dir__)
......@@ -41,7 +41,7 @@ module QA
# Create and connect K8s cluster
@cluster = Service::KubernetesCluster.new(rbac: rbac).create!
kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster|
kubernetes_cluster = Resource::KubernetesCluster.fabricate! do |cluster|
cluster.project = project
cluster.cluster = @cluster
cluster.install_helm_tiller = true
......
# frozen_string_literal: true
describe QA::Factory::Resource::User do
describe QA::Resource::User do
describe "#fabricate_via_api!" do
Response = Struct.new(:code, :body)
......
# frozen_string_literal: true
describe QA::Factory::ApiFabricator do
let(:factory_without_api_support) do
describe QA::Resource::ApiFabricator do
let(:resource_without_api_support) do
Class.new do
def self.name
'FooBarFactory'
'FooBarResource'
end
end
end
let(:factory_with_api_support) do
let(:resource_with_api_support) do
Class.new do
def self.name
'FooBarFactory'
'FooBarResource'
end
def api_get_path
......@@ -33,22 +33,22 @@ describe QA::Factory::ApiFabricator do
allow(subject).to receive(:current_url).and_return('')
end
subject { factory.tap { |f| f.include(described_class) }.new }
subject { resource.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 }
context 'when resource does not support fabrication via the API' do
let(:resource) { resource_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 }
context 'when resource supports fabrication via the API' do
let(:resource) { resource_with_api_support }
it 'returns false' do
expect(subject).to be_api_support
......@@ -67,20 +67,20 @@ describe QA::Factory::ApiFabricator do
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 }
context 'when resource does not support fabrication via the API' do
let(:resource) { resource_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!")
expect { subject.fabricate_via_api! }.to raise_error(NotImplementedError, "Resource FooBarResource does not support fabrication via the API!")
end
end
context 'when factory supports fabrication via the API' do
let(:factory) { factory_with_api_support }
context 'when resource supports fabrication via the API' do
let(:resource) { resource_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) }
let(:response) { { id: 1, name: 'John Doe', web_url: resource_web_url } }
let(:raw_post) { double('Raw POST response', code: 201, body: response.to_json) }
before do
stub_const('QA::Runtime::API::Request', api_request)
......@@ -103,7 +103,7 @@ describe QA::Factory::ApiFabricator do
it 'populates api_resource with the resource' do
subject.fabricate_via_api!
expect(subject.api_resource).to eq(resource)
expect(subject.api_resource).to eq(response)
end
context 'when the POST fails' do
......@@ -114,17 +114,17 @@ describe QA::Factory::ApiFabricator 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.fabricate_via_api! }.to raise_error(described_class::ResourceFabricationFailedError, "Fabrication of FooBarResource 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
let(:resource) do
Class.new do
def self.name
'FooBarFactory'
'FooBarResource'
end
def api_get_path
......@@ -146,12 +146,12 @@ describe QA::Factory::ApiFabricator do
end
end
let(:resource) { { existing: 'foo', web_url: resource_web_url } }
let(:response) { { 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)
expect(subject).to receive(:transform_api_resource).with(response).and_return(transformed_resource)
subject.fabricate_via_api!
end
......
# frozen_string_literal: true
describe QA::Factory::Base do
describe QA::Resource::Base do
include Support::StubENV
let(:factory) { spy('factory') }
let(:resource) { spy('resource') }
let(:location) { 'http://location' }
shared_context 'fabrication context' do
subject do
Class.new(described_class) do
def self.name
'MyFactory'
'MyResource'
end
end
end
before do
allow(subject).to receive(:current_url).and_return(location)
allow(subject).to receive(:new).and_return(factory)
allow(subject).to receive(:new).and_return(resource)
end
end
shared_examples 'fabrication method' do |fabrication_method_called, actual_fabrication_method = nil|
let(:fabrication_method_used) { actual_fabrication_method || fabrication_method_called }
it 'yields factory before calling factory method' do
expect(factory).to receive(:something!).ordered
expect(factory).to receive(fabrication_method_used).ordered.and_return(location)
it 'yields resource before calling resource method' do
expect(resource).to receive(:something!).ordered
expect(resource).to receive(fabrication_method_used).ordered.and_return(location)
subject.public_send(fabrication_method_called, factory: factory) do |factory|
factory.something!
subject.public_send(fabrication_method_called, resource: resource) do |resource|
resource.something!
end
end
it 'does not log the factory and build method when QA_DEBUG=false' do
it 'does not log the resource and build method when QA_DEBUG=false' do
stub_env('QA_DEBUG', 'false')
expect(factory).to receive(fabrication_method_used).and_return(location)
expect(resource).to receive(fabrication_method_used).and_return(location)
expect { subject.public_send(fabrication_method_called, 'something', factory: factory) }
expect { subject.public_send(fabrication_method_called, 'something', resource: resource) }
.not_to output.to_stdout
end
end
describe '.fabricate!' do
context 'when factory does not support fabrication via the API' do
context 'when resource does not support fabrication via the API' do
before do
expect(described_class).to receive(:fabricate_via_api!).and_raise(NotImplementedError)
end
......@@ -55,7 +55,7 @@ describe QA::Factory::Base do
end
end
context 'when factory supports fabrication via the API' do
context 'when resource supports fabrication via the API' do
it 'calls .fabricate_via_browser_ui!' do
expect(described_class).to receive(:fabricate_via_api!)
......@@ -69,20 +69,20 @@ describe QA::Factory::Base do
it_behaves_like 'fabrication method', :fabricate_via_api!
it 'instantiates the factory, calls factory method returns the resource' do
expect(factory).to receive(:fabricate_via_api!).and_return(location)
it 'instantiates the resource, calls resource method returns the resource' do
expect(resource).to receive(:fabricate_via_api!).and_return(location)
result = subject.fabricate_via_api!(factory: factory, parents: [])
result = subject.fabricate_via_api!(resource: resource, parents: [])
expect(result).to eq(factory)
expect(result).to eq(resource)
end
it 'logs the factory and build method when QA_DEBUG=true' do
it 'logs the resource and build method when QA_DEBUG=true' do
stub_env('QA_DEBUG', 'true')
expect(factory).to receive(:fabricate_via_api!).and_return(location)
expect(resource).to receive(:fabricate_via_api!).and_return(location)
expect { subject.fabricate_via_api!('something', factory: factory, parents: []) }
.to output(/==> Built a MyFactory via api in [\d\.\-e]+ seconds+/)
expect { subject.fabricate_via_api!('something', resource: resource, parents: []) }
.to output(/==> Built a MyResource via api in [\d\.\-e]+ seconds+/)
.to_stdout
end
end
......@@ -92,30 +92,30 @@ describe QA::Factory::Base do
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: [])
it 'instantiates the resource and calls resource method' do
subject.fabricate_via_browser_ui!('something', resource: resource, parents: [])
expect(factory).to have_received(:fabricate!).with('something')
expect(resource).to have_received(:fabricate!).with('something')
end
it 'returns fabrication resource' do
result = subject.fabricate_via_browser_ui!('something', factory: factory, parents: [])
result = subject.fabricate_via_browser_ui!('something', resource: resource, parents: [])
expect(result).to eq(factory)
expect(result).to eq(resource)
end
it 'logs the factory and build method when QA_DEBUG=true' do
it 'logs the resource and build method when QA_DEBUG=true' do
stub_env('QA_DEBUG', 'true')
expect { subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) }
.to output(/==> Built a MyFactory via browser_ui in [\d\.\-e]+ seconds+/)
expect { subject.fabricate_via_browser_ui!('something', resource: resource, parents: []) }
.to output(/==> Built a MyResource via browser_ui in [\d\.\-e]+ seconds+/)
.to_stdout
end
end
shared_context 'simple factory' do
shared_context 'simple resource' do
subject do
Class.new(QA::Factory::Base) do
Class.new(QA::Resource::Base) do
attribute :test do
'block'
end
......@@ -132,11 +132,11 @@ describe QA::Factory::Base do
end
end
let(:factory) { subject.new }
let(:resource) { subject.new }
end
describe '.attribute' do
include_context 'simple factory'
include_context 'simple resource'
it 'appends new attribute' do
expect(subject.attributes_names).to eq([:no_block, :test, :web_url])
......@@ -144,7 +144,7 @@ describe QA::Factory::Base do
context 'when the attribute is populated via a block' do
it 'returns value from the block' do
result = subject.fabricate!(factory: factory)
result = subject.fabricate!(resource: resource)
expect(result).to be_a(described_class)
expect(result.test).to eq('block')
......@@ -155,11 +155,11 @@ describe QA::Factory::Base do
let(:api_resource) { { no_block: 'api' } }
before do
expect(factory).to receive(:api_resource).and_return(api_resource)
expect(resource).to receive(:api_resource).and_return(api_resource)
end
it 'returns value from api' do
result = subject.fabricate!(factory: factory)
result = subject.fabricate!(resource: resource)
expect(result).to be_a(described_class)
expect(result.no_block).to eq('api')
......@@ -173,7 +173,7 @@ describe QA::Factory::Base do
end
it 'returns value from api and emits an INFO log entry' do
result = subject.fabricate!(factory: factory)
result = subject.fabricate!(resource: resource)
expect(result).to be_a(described_class)
expect(result.test).to eq('api_with_block')
......@@ -185,11 +185,11 @@ describe QA::Factory::Base do
context 'when the attribute is populated via direct assignment' do
before do
factory.test = 'value'
resource.test = 'value'
end
it 'returns value from the assignment' do
result = subject.fabricate!(factory: factory)
result = subject.fabricate!(resource: resource)
expect(result).to be_a(described_class)
expect(result.test).to eq('value')
......@@ -197,11 +197,11 @@ describe QA::Factory::Base do
context 'when the api also has such response' do
before do
allow(factory).to receive(:api_resource).and_return({ test: 'api' })
allow(resource).to receive(:api_resource).and_return({ test: 'api' })
end
it 'returns value from the assignment' do
result = subject.fabricate!(factory: factory)
result = subject.fabricate!(resource: resource)
expect(result).to be_a(described_class)
expect(result.test).to eq('value')
......@@ -211,36 +211,36 @@ describe QA::Factory::Base do
context 'when the attribute has no value' do
it 'raises an error because no values could be found' do
result = subject.fabricate!(factory: factory)
result = subject.fabricate!(resource: resource)
expect { result.no_block }
.to raise_error(described_class::NoValueError, "No value was computed for no_block of #{factory.class.name}.")
.to raise_error(described_class::NoValueError, "No value was computed for no_block of #{resource.class.name}.")
end
end
end
describe '#web_url' do
include_context 'simple factory'
include_context 'simple resource'
it 'sets #web_url to #current_url after fabrication' do
subject.fabricate!(factory: factory)
subject.fabricate!(resource: resource)
expect(factory.web_url).to eq(subject.current_url)
expect(resource.web_url).to eq(subject.current_url)
end
end
describe '#visit!' do
include_context 'simple factory'
include_context 'simple resource'
before do
allow(factory).to receive(:visit)
allow(resource).to receive(:visit)
end
it 'calls #visit with the underlying #web_url' do
factory.web_url = subject.current_url
factory.visit!
resource.web_url = subject.current_url
resource.visit!
expect(factory).to have_received(:visit).with(subject.current_url)
expect(resource).to have_received(:visit).with(subject.current_url)
end
end
end
# frozen_string_literal: true
describe QA::Factory::Repository::Push do
describe QA::Resource::Repository::Push do
describe '.files=' do
let(:files) 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