Always use attribute
to define the product
This commit is contained in:
parent
bf96ec85c7
commit
5151801964
34 changed files with 557 additions and 637 deletions
1
qa/qa.rb
1
qa/qa.rb
|
@ -39,7 +39,6 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
autoload :ApiFabricator, 'qa/factory/api_fabricator'
|
autoload :ApiFabricator, 'qa/factory/api_fabricator'
|
||||||
autoload :Base, 'qa/factory/base'
|
autoload :Base, 'qa/factory/base'
|
||||||
autoload :Dependency, 'qa/factory/dependency'
|
|
||||||
autoload :Product, 'qa/factory/product'
|
autoload :Product, 'qa/factory/product'
|
||||||
|
|
||||||
module Resource
|
module Resource
|
||||||
|
|
|
@ -26,11 +26,7 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Shirt < Factory::Base
|
class Shirt < Factory::Base
|
||||||
attr_accessor :name, :size
|
attr_accessor :name
|
||||||
|
|
||||||
def initialize(name)
|
|
||||||
@name = name
|
|
||||||
end
|
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
Page::Dashboard::Index.perform do |dashboard_index|
|
Page::Dashboard::Index.perform do |dashboard_index|
|
||||||
|
@ -64,21 +60,10 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Shirt < Factory::Base
|
class Shirt < Factory::Base
|
||||||
attr_accessor :name, :size
|
attr_accessor :name
|
||||||
|
|
||||||
def initialize(name)
|
|
||||||
@name = name
|
|
||||||
end
|
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
Page::Dashboard::Index.perform do |dashboard_index|
|
# ... same as before
|
||||||
dashboard_index.go_to_new_shirt
|
|
||||||
end
|
|
||||||
|
|
||||||
Page::Shirt::New.perform do |shirt_new|
|
|
||||||
shirt_new.set_name(name)
|
|
||||||
shirt_new.create_shirt!
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_get_path
|
def api_get_path
|
||||||
|
@ -103,33 +88,69 @@ end
|
||||||
The [`Project` factory](./resource/project.rb) is a good real example of Browser
|
The [`Project` factory](./resource/project.rb) is a good real example of Browser
|
||||||
UI and API implementations.
|
UI and API implementations.
|
||||||
|
|
||||||
### Define dependencies
|
### Define attributes
|
||||||
|
|
||||||
A resource may need an other resource to exist first. For instance, a project
|
After the resource is fabricated, we would like to access the attributes on
|
||||||
needs a group to be created in.
|
the resource. We define the attributes with `attribute` method. Suppose
|
||||||
|
we want to access the name on the resource, we could change `attr_accessor`
|
||||||
To define a dependency, you can use the `dependency` DSL method.
|
to `attribute`:
|
||||||
The first argument is a factory class, then you should pass `as: <name>` to give
|
|
||||||
a name to the dependency.
|
|
||||||
That will allow access to the dependency 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` dependency to it:
|
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
module QA
|
module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Shirt < Factory::Base
|
class Shirt < Factory::Base
|
||||||
attr_accessor :name, :size
|
attribute :name
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
# ... same as before
|
||||||
project.name = 'project-to-create-a-shirt'
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The difference between `attr_accessor` and `attribute` is that by using
|
||||||
|
`attribute` it can also be accessed from the product:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
shirt =
|
||||||
|
QA::Factory::Resource::Shirt.fabricate! do |resource|
|
||||||
|
resource.name = "GitLab QA"
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(name)
|
shirt.name # => "GitLab QA"
|
||||||
@name = name
|
```
|
||||||
|
|
||||||
|
In the above example, if we use `attr_accessor :name` then `shirt.name` won't
|
||||||
|
be available. On the other hand, using `attribute :name` will allow you to use
|
||||||
|
`shirt.name`, so most of the time you'll want to use `attribute` instead of
|
||||||
|
`attr_accessor` unless we clearly don't need it for the product.
|
||||||
|
|
||||||
|
#### Resource attributes
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
module QA
|
||||||
|
module Factory
|
||||||
|
module Resource
|
||||||
|
class Shirt < Factory::Base
|
||||||
|
attribute :name
|
||||||
|
|
||||||
|
attribute :project do
|
||||||
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
|
resource.name = 'project-to-create-a-shirt'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
|
@ -164,19 +185,19 @@ module QA
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note that dependencies are always built via the API fabrication method if
|
**Note that all the attributes are lazily constructed. This means if you want
|
||||||
supported by their factories.**
|
a specific attribute to be fabricated first, you'll need to call the
|
||||||
|
attribute method first even if you're not using it.**
|
||||||
|
|
||||||
### Define attributes on the created resource
|
#### Product data attributes
|
||||||
|
|
||||||
Once created, you may want to populate a resource with attributes that can be
|
Once created, you may want to populate a resource with attributes that can be
|
||||||
found in the Web page, or in the API response.
|
found in the Web page, or in the API response.
|
||||||
For instance, once you create a project, you may want to store its repository
|
For instance, once you create a project, you may want to store its repository
|
||||||
SSH URL as an attribute.
|
SSH URL as an attribute.
|
||||||
|
|
||||||
To define an attribute, you can use the `product` DSL method.
|
Again we could use the `attribute` method with a block, using a page object
|
||||||
The first argument is the attribute name, then you should define a name for the
|
to retrieve the data on the page.
|
||||||
dependency to be accessible from your resource object's methods.
|
|
||||||
|
|
||||||
Let's take the `Shirt` factory, and define a `:brand` attribute:
|
Let's take the `Shirt` factory, and define a `:brand` attribute:
|
||||||
|
|
||||||
|
@ -185,90 +206,74 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Shirt < Factory::Base
|
class Shirt < Factory::Base
|
||||||
attr_accessor :name, :size
|
attribute :name
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-to-create-a-shirt'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
|
resource.name = 'project-to-create-a-shirt'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Attribute populated from the Browser UI (using the block)
|
# Attribute populated from the Browser UI (using the block)
|
||||||
product :brand do
|
attribute :brand do
|
||||||
Page::Shirt::Show.perform do |shirt_show|
|
Page::Shirt::Show.perform do |shirt_show|
|
||||||
shirt_show.fetch_brand_from_page
|
shirt_show.fetch_brand_from_page
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(name)
|
# ... same as before
|
||||||
@name = name
|
|
||||||
end
|
|
||||||
|
|
||||||
def fabricate!
|
|
||||||
project.visit!
|
|
||||||
|
|
||||||
Page::Project::Show.perform do |project_show|
|
|
||||||
project_show.go_to_new_shirt
|
|
||||||
end
|
|
||||||
|
|
||||||
Page::Shirt::New.perform do |shirt_new|
|
|
||||||
shirt_new.set_name(name)
|
|
||||||
shirt_new.create_shirt!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_get_path
|
|
||||||
"/project/#{project.path}/shirt/#{name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_post_path
|
|
||||||
"/project/#{project.path}/shirts"
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_post_body
|
|
||||||
{
|
|
||||||
name: name
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Inherit a factory's attribute
|
**Note again that all the attributes are lazily constructed. This means if
|
||||||
|
you call `shirt.brand` after moving to the other page, it'll not properly
|
||||||
|
retrieve the data because we're no longer on the expected page.**
|
||||||
|
|
||||||
Sometimes, you want a resource to inherit its factory attributes. For instance,
|
Consider this:
|
||||||
it could be useful to pass the `size` attribute from the `Shirt` factory to the
|
|
||||||
created resource.
|
|
||||||
You can do that by defining `product :attribute_name` without a block.
|
|
||||||
|
|
||||||
Let's take the `Shirt` factory, and define a `:name` and a `:size` attributes:
|
```ruby
|
||||||
|
shirt =
|
||||||
|
QA::Factory::Resource::Shirt.fabricate! do |resource|
|
||||||
|
resource.name = "GitLab QA"
|
||||||
|
end
|
||||||
|
|
||||||
|
shirt.project.visit!
|
||||||
|
|
||||||
|
shirt.brand # => FAIL!
|
||||||
|
```
|
||||||
|
|
||||||
|
The above example will fail because now we're on the project page, trying to
|
||||||
|
construct the brand data from the shirt page, however we moved to the project
|
||||||
|
page already. There are two ways to solve this, one is that we could try to
|
||||||
|
retrieve the brand before visiting the project again:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
shirt =
|
||||||
|
QA::Factory::Resource::Shirt.fabricate! do |resource|
|
||||||
|
resource.name = "GitLab QA"
|
||||||
|
end
|
||||||
|
|
||||||
|
shirt.brand # => OK!
|
||||||
|
|
||||||
|
shirt.project.visit!
|
||||||
|
|
||||||
|
shirt.brand # => OK!
|
||||||
|
```
|
||||||
|
|
||||||
|
The attribute will be stored in the instance therefore all the following calls
|
||||||
|
will be fine, using the data previously constructed. If we think that this
|
||||||
|
might be too brittle, we could eagerly construct the data right before
|
||||||
|
ending fabrication:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
module QA
|
module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Shirt < Factory::Base
|
class Shirt < Factory::Base
|
||||||
attr_accessor :name, :size
|
# ... same as before
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
|
||||||
project.name = 'project-to-create-a-shirt'
|
|
||||||
end
|
|
||||||
|
|
||||||
# Attribute from the Browser UI (using the block)
|
|
||||||
product :brand do
|
|
||||||
Page::Shirt::Show.perform do |shirt_show|
|
|
||||||
shirt_show.fetch_brand_from_page
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Attribute inherited from the Shirt factory if present,
|
|
||||||
# or a QA::Factory::Product::NoValueError is raised otherwise
|
|
||||||
product :name
|
|
||||||
product :size
|
|
||||||
|
|
||||||
def initialize(name)
|
|
||||||
@name = name
|
|
||||||
end
|
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
project.visit!
|
project.visit!
|
||||||
|
@ -281,20 +286,8 @@ module QA
|
||||||
shirt_new.set_name(name)
|
shirt_new.set_name(name)
|
||||||
shirt_new.create_shirt!
|
shirt_new.create_shirt!
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def api_get_path
|
brand # Eagerly construct the data
|
||||||
"/project/#{project.path}/shirt/#{name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_post_path
|
|
||||||
"/project/#{project.path}/shirts"
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_post_body
|
|
||||||
{
|
|
||||||
name: name
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -302,6 +295,48 @@ module QA
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This will make sure we construct the data right after we created the shirt.
|
||||||
|
The drawback for this will become we're forced to construct the data even
|
||||||
|
if we don't really need to use it.
|
||||||
|
|
||||||
|
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
|
||||||
|
attribute :name
|
||||||
|
|
||||||
|
attribute :project do
|
||||||
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
|
resource.name = 'project-to-create-a-shirt'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Attribute populated from the Browser UI (using the block)
|
||||||
|
attribute :brand do
|
||||||
|
back_url = current_url
|
||||||
|
visit!
|
||||||
|
|
||||||
|
Page::Shirt::Show.perform do |shirt_show|
|
||||||
|
shirt_show.fetch_brand_from_page
|
||||||
|
end
|
||||||
|
|
||||||
|
visit(back_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
# ... same as before
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This will make sure it's on the shirt page before constructing brand, and
|
||||||
|
move back to the previous page to avoid breaking the state.
|
||||||
|
|
||||||
#### Define an attribute based on an API response
|
#### Define an attribute based on an API response
|
||||||
|
|
||||||
Sometimes, you want to define a resource attribute based on the API response
|
Sometimes, you want to define a resource attribute based on the API response
|
||||||
|
@ -311,7 +346,6 @@ the API returns
|
||||||
```ruby
|
```ruby
|
||||||
{
|
{
|
||||||
brand: 'a-brand-new-brand',
|
brand: 'a-brand-new-brand',
|
||||||
size: 'extra-small',
|
|
||||||
style: 't-shirt',
|
style: 't-shirt',
|
||||||
materials: [[:cotton, 80], [:polyamide, 20]]
|
materials: [[:cotton, 80], [:polyamide, 20]]
|
||||||
}
|
}
|
||||||
|
@ -320,18 +354,6 @@ the API returns
|
||||||
you may want to store `style` as-is in the resource, and fetch the first value
|
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.
|
of the first `materials` item in a `main_fabric` attribute.
|
||||||
|
|
||||||
For both attributes, you will need to define an inherited attribute, as shown
|
|
||||||
in "Inherit a factory's attribute" above, but in the case of `main_fabric`, you
|
|
||||||
will need to implement the
|
|
||||||
`#transform_api_resource` method to first populate the `:main_fabric` key in the
|
|
||||||
API response so that it can be used later to automatically populate the
|
|
||||||
attribute on your resource.
|
|
||||||
|
|
||||||
If an attribute can only be retrieved from the API response, you should define
|
|
||||||
a block to give it a default value, otherwise you could get a
|
|
||||||
`QA::Factory::Product::NoValueError` when creating your resource via the
|
|
||||||
Browser UI.
|
|
||||||
|
|
||||||
Let's take the `Shirt` factory, and define a `:style` and a `:main_fabric`
|
Let's take the `Shirt` factory, and define a `:style` and a `:main_fabric`
|
||||||
attributes:
|
attributes:
|
||||||
|
|
||||||
|
@ -340,69 +362,21 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Shirt < Factory::Base
|
class Shirt < Factory::Base
|
||||||
attr_accessor :name, :size
|
# ... same as before
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
# Attribute from the Shirt factory if present,
|
||||||
project.name = 'project-to-create-a-shirt'
|
# or fetched from the API response if present,
|
||||||
|
# or a QA::Factory::Base::NoValueError is raised otherwise
|
||||||
|
attribute :style
|
||||||
|
|
||||||
|
# If the attribute from the Shirt factory 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.
|
||||||
|
attribute :main_fabric do
|
||||||
|
api_response.&dig(:materials, 0, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Attribute fetched from the API response if present,
|
# ... same as before
|
||||||
# or from the Browser UI otherwise (using the block)
|
|
||||||
product :brand do
|
|
||||||
Page::Shirt::Show.perform do |shirt_show|
|
|
||||||
shirt_show.fetch_brand_from_page
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Attribute fetched from the API response if present,
|
|
||||||
# or from the Shirt factory if present,
|
|
||||||
# or a QA::Factory::Product::NoValueError is raised otherwise
|
|
||||||
product :name
|
|
||||||
product :size
|
|
||||||
product :style do
|
|
||||||
'unknown'
|
|
||||||
end
|
|
||||||
product :main_fabric do
|
|
||||||
'unknown'
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(name)
|
|
||||||
@name = name
|
|
||||||
end
|
|
||||||
|
|
||||||
def fabricate!
|
|
||||||
project.visit!
|
|
||||||
|
|
||||||
Page::Project::Show.perform do |project_show|
|
|
||||||
project_show.go_to_new_shirt
|
|
||||||
end
|
|
||||||
|
|
||||||
Page::Shirt::New.perform do |shirt_new|
|
|
||||||
shirt_new.set_name(name)
|
|
||||||
shirt_new.create_shirt!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_get_path
|
|
||||||
"/project/#{project.path}/shirt/#{name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_post_path
|
|
||||||
"/project/#{project.path}/shirts"
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_post_body
|
|
||||||
{
|
|
||||||
name: name
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def transform_api_resource(api_response)
|
|
||||||
api_response[:main_fabric] = api_response[:materials][0][0]
|
|
||||||
api_response
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -411,11 +385,10 @@ end
|
||||||
|
|
||||||
**Notes on attributes precedence:**
|
**Notes on attributes precedence:**
|
||||||
|
|
||||||
|
- attributes from the factory have the highest precedence
|
||||||
- attributes from the API response take precedence over attributes from the
|
- attributes from the API response take precedence over attributes from the
|
||||||
Browser UI
|
block (usually from Browser UI)
|
||||||
- attributes from the Browser UI take precedence over attributes from the
|
- attributes without a value will raise a `QA::Factory::Base::NoValueError` error
|
||||||
factory (i.e inherited)
|
|
||||||
- attributes without a value will raise a `QA::Factory::Product::NoValueError` error
|
|
||||||
|
|
||||||
## Creating resources in your tests
|
## Creating resources in your tests
|
||||||
|
|
||||||
|
@ -428,42 +401,40 @@ Here is an example that will use the API fabrication method under the hood since
|
||||||
it's supported by the `Shirt` factory:
|
it's supported by the `Shirt` factory:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
my_shirt = Factory::Resource::Shirt.fabricate!('my-shirt') do |shirt|
|
my_shirt = Factory::Resource::Shirt.fabricate! do |shirt|
|
||||||
shirt.size = 'small'
|
shirt.name = 'my-shirt'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
expect(page).to have_text(my_shirt.name) # => "my-shirt" from the factory's attribute
|
||||||
expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response
|
expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response
|
||||||
expect(page).to have_text(my_shirt.name) # => "my-shirt" from the inherited factory's attribute
|
|
||||||
expect(page).to have_text(my_shirt.size) # => "extra-small" 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.style) # => "t-shirt" from the API response
|
||||||
expect(page).to have_text(my_shirt.main_fabric) # => "cotton" from the (transformed) API response
|
expect(page).to have_text(my_shirt.main_fabric) # => "cotton" from the API response via the block
|
||||||
```
|
```
|
||||||
|
|
||||||
If you explicitely want to use the Browser UI fabrication method, you can call
|
If you explicitly want to use the Browser UI fabrication method, you can call
|
||||||
the `.fabricate_via_browser_ui!` method instead:
|
the `.fabricate_via_browser_ui!` method instead:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
my_shirt = Factory::Resource::Shirt.fabricate_via_browser_ui!('my-shirt') do |shirt|
|
my_shirt = Factory::Resource::Shirt.fabricate_via_browser_ui! do |shirt|
|
||||||
shirt.size = 'small'
|
shirt.name = 'my-shirt'
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(page).to have_text(my_shirt.brand) # => the brand name fetched from the `Page::Shirt::Show` page
|
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 inherited factory's attribute
|
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.size) # => "small" from the inherited factory's attribute
|
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.style) # => "unknown" from the attribute block
|
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.main_fabric) # => "unknown" from the attribute block
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also explicitely use the API fabrication method, by calling the
|
You can also explicitly use the API fabrication method, by calling the
|
||||||
`.fabricate_via_api!` method:
|
`.fabricate_via_api!` method:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
my_shirt = Factory::Resource::Shirt.fabricate_via_api!('my-shirt') do |shirt|
|
my_shirt = Factory::Resource::Shirt.fabricate_via_api! do |shirt|
|
||||||
shirt.size = 'small'
|
shirt.name = 'my-shirt'
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
In this case, the result will be similar to calling `Factory::Resource::Shirt.fabricate!('my-shirt')`.
|
In this case, the result will be similar to calling `Factory::Resource::Shirt.fabricate!`.
|
||||||
|
|
||||||
## Where to ask for help?
|
## Where to ask for help?
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,42 @@ module QA
|
||||||
include ApiFabricator
|
include ApiFabricator
|
||||||
extend Capybara::DSL
|
extend Capybara::DSL
|
||||||
|
|
||||||
def_delegators :evaluator, :dependency, :dependencies
|
NoValueError = Class.new(RuntimeError)
|
||||||
def_delegators :evaluator, :product, :attributes
|
|
||||||
|
def_delegators :evaluator, :attribute
|
||||||
|
|
||||||
def fabricate!(*_args)
|
def fabricate!(*_args)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def visit!
|
||||||
|
visit(web_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def populate_attribute(name, block)
|
||||||
|
value = attribute_value(name, block)
|
||||||
|
|
||||||
|
raise NoValueError, "No value was computed for product #{name} of factory #{self.class.name}." unless value
|
||||||
|
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
|
def attribute_value(name, block)
|
||||||
|
api_value = api_resource&.dig(name)
|
||||||
|
|
||||||
|
if api_value && block
|
||||||
|
log_having_both_api_result_and_block(name, api_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
api_value || (block && instance_exec(&block))
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_having_both_api_result_and_block(name, api_value)
|
||||||
|
QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored."
|
||||||
|
end
|
||||||
|
|
||||||
def self.fabricate!(*args, &prepare_block)
|
def self.fabricate!(*args, &prepare_block)
|
||||||
fabricate_via_api!(*args, &prepare_block)
|
fabricate_via_api!(*args, &prepare_block)
|
||||||
rescue NotImplementedError
|
rescue NotImplementedError
|
||||||
|
@ -52,13 +81,10 @@ module QA
|
||||||
def self.do_fabricate!(factory:, prepare_block:, parents: [])
|
def self.do_fabricate!(factory:, prepare_block:, parents: [])
|
||||||
prepare_block.call(factory) if prepare_block
|
prepare_block.call(factory) if prepare_block
|
||||||
|
|
||||||
dependencies.each do |signature|
|
|
||||||
Factory::Dependency.new(factory, signature).build!(parents: parents + [self])
|
|
||||||
end
|
|
||||||
|
|
||||||
resource_web_url = yield
|
resource_web_url = yield
|
||||||
|
factory.web_url = resource_web_url
|
||||||
|
|
||||||
Factory::Product.populate!(factory, resource_web_url)
|
Factory::Product.new(factory)
|
||||||
end
|
end
|
||||||
private_class_method :do_fabricate!
|
private_class_method :do_fabricate!
|
||||||
|
|
||||||
|
@ -85,31 +111,40 @@ module QA
|
||||||
end
|
end
|
||||||
private_class_method :evaluator
|
private_class_method :evaluator
|
||||||
|
|
||||||
class DSL
|
def self.dynamic_attributes
|
||||||
attr_reader :dependencies, :attributes
|
const_get(:DynamicAttributes)
|
||||||
|
rescue NameError
|
||||||
|
mod = const_set(:DynamicAttributes, Module.new)
|
||||||
|
|
||||||
|
include mod
|
||||||
|
|
||||||
|
mod
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.attributes_names
|
||||||
|
dynamic_attributes.instance_methods(false).sort.grep_v(/=$/)
|
||||||
|
end
|
||||||
|
|
||||||
|
class DSL
|
||||||
def initialize(base)
|
def initialize(base)
|
||||||
@base = base
|
@base = base
|
||||||
@dependencies = []
|
|
||||||
@attributes = []
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def dependency(factory, as:, &block)
|
def attribute(name, &block)
|
||||||
as.tap do |name|
|
@base.dynamic_attributes.module_eval do
|
||||||
@base.class_eval { attr_accessor name }
|
attr_writer(name)
|
||||||
|
|
||||||
Dependency::Signature.new(name, factory, block).tap do |signature|
|
define_method(name) do
|
||||||
@dependencies << signature
|
instance_variable_get("@#{name}") ||
|
||||||
|
instance_variable_set(
|
||||||
|
"@#{name}",
|
||||||
|
populate_attribute(name, block))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def product(attribute, &block)
|
attribute :web_url
|
||||||
Product::Attribute.new(attribute, block).tap do |signature|
|
|
||||||
@attributes << signature
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
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
|
class Product
|
||||||
include Capybara::DSL
|
include Capybara::DSL
|
||||||
|
|
||||||
NoValueError = Class.new(RuntimeError)
|
attr_reader :factory
|
||||||
|
|
||||||
attr_reader :factory, :web_url
|
def initialize(factory)
|
||||||
|
|
||||||
Attribute = Struct.new(:name, :block)
|
|
||||||
|
|
||||||
def initialize(factory, web_url)
|
|
||||||
@factory = factory
|
@factory = factory
|
||||||
@web_url = web_url
|
|
||||||
|
|
||||||
populate_attributes!
|
define_attributes
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit!
|
def visit!
|
||||||
visit(web_url)
|
visit(web_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.populate!(factory, web_url)
|
def populate(*attributes)
|
||||||
new(factory, web_url)
|
attributes.each(&method(:public_send))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def populate_attributes!
|
def define_attributes
|
||||||
factory.class.attributes.each do |attribute|
|
factory.class.attributes_names.each do |name|
|
||||||
instance_exec(factory, attribute.block) do |factory, block|
|
define_singleton_method(name) do
|
||||||
value = attribute_value(attribute, block)
|
factory.public_send(name)
|
||||||
|
|
||||||
raise NoValueError, "No value was computed for product #{attribute.name} of factory #{factory.class.name}." unless value
|
|
||||||
|
|
||||||
define_singleton_method(attribute.name) { value }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def attribute_value(attribute, block)
|
|
||||||
factory.api_resource&.dig(attribute.name) ||
|
|
||||||
(block && block.call(factory)) ||
|
|
||||||
(factory.respond_to?(attribute.name) && factory.public_send(attribute.name))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,13 +2,14 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Repository
|
module Repository
|
||||||
class ProjectPush < Factory::Repository::Push
|
class ProjectPush < Factory::Repository::Push
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-with-code'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
project.description = 'Project with repository'
|
resource.name = 'project-with-code'
|
||||||
|
resource.description = 'Project with repository'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
product :output
|
attribute :output
|
||||||
product :project
|
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@file_name = 'file.txt'
|
@file_name = 'file.txt'
|
||||||
|
|
|
@ -2,10 +2,12 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Repository
|
module Repository
|
||||||
class WikiPush < Factory::Repository::Push
|
class WikiPush < Factory::Repository::Push
|
||||||
dependency Factory::Resource::Wiki, as: :wiki do |wiki|
|
attribute :wiki do
|
||||||
wiki.title = 'Home'
|
Factory::Resource::Wiki.fabricate! do |resource|
|
||||||
wiki.content = '# My First Wiki Content'
|
resource.title = 'Home'
|
||||||
wiki.message = 'Update home'
|
resource.content = '# My First Wiki Content'
|
||||||
|
resource.message = 'Update home'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
|
|
|
@ -5,8 +5,10 @@ module QA
|
||||||
attr_accessor :project, :branch_name,
|
attr_accessor :project, :branch_name,
|
||||||
:allow_to_push, :allow_to_merge, :protected
|
:allow_to_push, :allow_to_merge, :protected
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'protected-branch-project'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
|
resource.name = 'protected-branch-project'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
|
@ -43,9 +45,7 @@ module QA
|
||||||
# to `allow_to_push` variable.
|
# to `allow_to_push` variable.
|
||||||
return branch unless @protected
|
return branch unless @protected
|
||||||
|
|
||||||
Page::Project::Menu.act do
|
Page::Project::Menu.perform(&:click_repository_settings)
|
||||||
click_repository_settings
|
|
||||||
end
|
|
||||||
|
|
||||||
Page::Project::Settings::Repository.perform do |setting|
|
Page::Project::Settings::Repository.perform do |setting|
|
||||||
setting.expand_protected_branches do |page|
|
setting.expand_protected_branches do |page|
|
||||||
|
|
|
@ -4,11 +4,11 @@ module QA
|
||||||
class DeployKey < Factory::Base
|
class DeployKey < Factory::Base
|
||||||
attr_accessor :title, :key
|
attr_accessor :title, :key
|
||||||
|
|
||||||
product :fingerprint do |resource|
|
attribute :fingerprint do
|
||||||
Page::Project::Settings::Repository.act do
|
Page::Project::Settings::Repository.perform do |setting|
|
||||||
expand_deploy_keys do |key|
|
setting.expand_deploy_keys do |key|
|
||||||
key_offset = key.key_titles.index do |title|
|
key_offset = key.key_titles.index do |key_title|
|
||||||
title.text == resource.title
|
key_title.text == title
|
||||||
end
|
end
|
||||||
|
|
||||||
key.key_fingerprints[key_offset].text
|
key.key_fingerprints[key_offset].text
|
||||||
|
@ -16,17 +16,17 @@ module QA
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-to-deploy'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
project.description = 'project for adding deploy key test'
|
resource.name = 'project-to-deploy'
|
||||||
|
resource.description = 'project for adding deploy key test'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
project.visit!
|
project.visit!
|
||||||
|
|
||||||
Page::Project::Menu.act do
|
Page::Project::Menu.perform(&:click_repository_settings)
|
||||||
click_repository_settings
|
|
||||||
end
|
|
||||||
|
|
||||||
Page::Project::Settings::Repository.perform do |setting|
|
Page::Project::Settings::Repository.perform do |setting|
|
||||||
setting.expand_deploy_keys do |page|
|
setting.expand_deploy_keys do |page|
|
||||||
|
|
|
@ -4,25 +4,27 @@ module QA
|
||||||
class DeployToken < Factory::Base
|
class DeployToken < Factory::Base
|
||||||
attr_accessor :name, :expires_at
|
attr_accessor :name, :expires_at
|
||||||
|
|
||||||
product :username do |resource|
|
attribute :username do
|
||||||
Page::Project::Settings::Repository.act do
|
Page::Project::Settings::Repository.perform do |page|
|
||||||
expand_deploy_tokens do |token|
|
page.expand_deploy_tokens do |token|
|
||||||
token.token_username
|
token.token_username
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
product :password do |password|
|
attribute :password do
|
||||||
Page::Project::Settings::Repository.act do
|
Page::Project::Settings::Repository.perform do |page|
|
||||||
expand_deploy_tokens do |token|
|
page.expand_deploy_tokens do |token|
|
||||||
token.token_password
|
token.token_password
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-to-deploy'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
project.description = 'project for adding deploy token test'
|
resource.name = 'project-to-deploy'
|
||||||
|
resource.description = 'project for adding deploy token test'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
|
|
|
@ -8,8 +8,10 @@ module QA
|
||||||
:content,
|
:content,
|
||||||
:commit_message
|
:commit_message
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-with-new-file'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
|
resource.name = 'project-with-new-file'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
|
@ -21,7 +23,7 @@ module QA
|
||||||
def fabricate!
|
def fabricate!
|
||||||
project.visit!
|
project.visit!
|
||||||
|
|
||||||
Page::Project::Show.act { create_new_file! }
|
Page::Project::Show.perform(&:create_new_file!)
|
||||||
|
|
||||||
Page::File::Form.perform do |page|
|
Page::File::Form.perform do |page|
|
||||||
page.add_name(@name)
|
page.add_name(@name)
|
||||||
|
|
|
@ -2,16 +2,18 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Fork < Factory::Base
|
class Fork < Factory::Base
|
||||||
dependency Factory::Repository::ProjectPush, as: :push
|
attribute :push do
|
||||||
|
Factory::Repository::ProjectPush.fabricate!
|
||||||
|
end
|
||||||
|
|
||||||
dependency Factory::Resource::User, as: :user do |user|
|
attribute :user do
|
||||||
|
Factory::Resource::User.fabricate! do |resource|
|
||||||
if Runtime::Env.forker?
|
if Runtime::Env.forker?
|
||||||
user.username = Runtime::Env.forker_username
|
resource.username = Runtime::Env.forker_username
|
||||||
user.password = Runtime::Env.forker_password
|
resource.password = Runtime::Env.forker_password
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
product :user
|
|
||||||
|
|
||||||
def visit_project_with_retry
|
def visit_project_with_retry
|
||||||
# The user intermittently fails to stay signed in after visiting the
|
# The user intermittently fails to stay signed in after visiting the
|
||||||
|
@ -48,15 +50,20 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
|
push
|
||||||
|
user
|
||||||
|
|
||||||
visit_project_with_retry
|
visit_project_with_retry
|
||||||
|
|
||||||
Page::Project::Show.act { fork_project }
|
Page::Project::Show.perform(&:fork_project)
|
||||||
|
|
||||||
Page::Project::Fork::New.perform do |fork_new|
|
Page::Project::Fork::New.perform do |fork_new|
|
||||||
fork_new.choose_namespace(user.name)
|
fork_new.choose_namespace(user.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
Page::Layout::Banner.act { has_notice?('The project was successfully forked.') }
|
Page::Layout::Banner.perform do |page|
|
||||||
|
page.has_notice?('The project was successfully forked.')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,12 +4,12 @@ module QA
|
||||||
class Group < Factory::Base
|
class Group < Factory::Base
|
||||||
attr_accessor :path, :description
|
attr_accessor :path, :description
|
||||||
|
|
||||||
dependency Factory::Resource::Sandbox, as: :sandbox
|
attribute :sandbox do
|
||||||
|
Factory::Resource::Sandbox.fabricate!
|
||||||
product :id do
|
|
||||||
true # We don't retrieve the Group ID when using the Browser UI
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attribute :id
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@path = Runtime::Namespace.name
|
@path = Runtime::Namespace.name
|
||||||
@description = "QA test run at #{Runtime::Namespace.time}"
|
@description = "QA test run at #{Runtime::Namespace.time}"
|
||||||
|
|
|
@ -2,22 +2,21 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Issue < Factory::Base
|
class Issue < Factory::Base
|
||||||
attr_accessor :title, :description, :project
|
attr_writer :description
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-for-issues'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
project.description = 'project for adding issues'
|
resource.name = 'project-for-issues'
|
||||||
|
resource.description = 'project for adding issues'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
product :project
|
attribute :title
|
||||||
product :title
|
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
project.visit!
|
project.visit!
|
||||||
|
|
||||||
Page::Project::Show.act do
|
Page::Project::Show.perform(&:go_to_new_issue)
|
||||||
go_to_new_issue
|
|
||||||
end
|
|
||||||
|
|
||||||
Page::Project::Issue::New.perform do |page|
|
Page::Project::Issue::New.perform do |page|
|
||||||
page.add_title(@title)
|
page.add_title(@title)
|
||||||
|
|
|
@ -7,24 +7,21 @@ module QA
|
||||||
attr_writer :project, :cluster,
|
attr_writer :project, :cluster,
|
||||||
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
|
:install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
|
||||||
|
|
||||||
product :ingress_ip do
|
attribute :ingress_ip do
|
||||||
Page::Project::Operations::Kubernetes::Show.perform do |page|
|
Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
|
||||||
page.ingress_ip
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
@project.visit!
|
@project.visit!
|
||||||
|
|
||||||
Page::Project::Menu.act { click_operations_kubernetes }
|
Page::Project::Menu.perform(
|
||||||
|
&:click_operations_kubernetes)
|
||||||
|
|
||||||
Page::Project::Operations::Kubernetes::Index.perform do |page|
|
Page::Project::Operations::Kubernetes::Index.perform(
|
||||||
page.add_kubernetes_cluster
|
&:add_kubernetes_cluster)
|
||||||
end
|
|
||||||
|
|
||||||
Page::Project::Operations::Kubernetes::Add.perform do |page|
|
Page::Project::Operations::Kubernetes::Add.perform(
|
||||||
page.add_existing_cluster
|
&:add_existing_cluster)
|
||||||
end
|
|
||||||
|
|
||||||
Page::Project::Operations::Kubernetes::AddExisting.perform do |page|
|
Page::Project::Operations::Kubernetes::AddExisting.perform do |page|
|
||||||
page.set_cluster_name(@cluster.cluster_name)
|
page.set_cluster_name(@cluster.cluster_name)
|
||||||
|
|
|
@ -4,14 +4,14 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Label < Factory::Base
|
class Label < Factory::Base
|
||||||
attr_accessor :title,
|
attr_accessor :description, :color
|
||||||
:description,
|
|
||||||
:color
|
|
||||||
|
|
||||||
product(:title) { |factory| factory.title }
|
attribute :title
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-with-label'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
|
resource.name = 'project-with-label'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
|
@ -23,8 +23,8 @@ module QA
|
||||||
def fabricate!
|
def fabricate!
|
||||||
project.visit!
|
project.visit!
|
||||||
|
|
||||||
Page::Project::Menu.act { go_to_labels }
|
Page::Project::Menu.perform(&:go_to_labels)
|
||||||
Page::Label::Index.act { go_to_new_label }
|
Page::Label::Index.perform(&:go_to_new_label)
|
||||||
|
|
||||||
Page::Label::New.perform do |page|
|
Page::Label::New.perform do |page|
|
||||||
page.fill_title(@title)
|
page.fill_title(@title)
|
||||||
|
|
|
@ -12,27 +12,33 @@ module QA
|
||||||
:milestone,
|
:milestone,
|
||||||
:labels
|
:labels
|
||||||
|
|
||||||
product :project
|
attribute :source_branch
|
||||||
product :source_branch
|
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-with-merge-request'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
|
resource.name = 'project-with-merge-request'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
dependency Factory::Repository::ProjectPush, as: :target do |push, factory|
|
attribute :target do
|
||||||
factory.project.visit!
|
project.visit!
|
||||||
push.project = factory.project
|
|
||||||
push.branch_name = 'master'
|
Factory::Repository::ProjectPush.fabricate! do |resource|
|
||||||
push.remote_branch = factory.target_branch
|
resource.project = project
|
||||||
|
resource.branch_name = 'master'
|
||||||
|
resource.remote_branch = target_branch
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
dependency Factory::Repository::ProjectPush, as: :source do |push, factory|
|
attribute :source do
|
||||||
push.project = factory.project
|
Factory::Repository::ProjectPush.fabricate! do |resource|
|
||||||
push.branch_name = factory.target_branch
|
resource.project = project
|
||||||
push.remote_branch = factory.source_branch
|
resource.branch_name = target_branch
|
||||||
push.new_branch = false
|
resource.remote_branch = source_branch
|
||||||
push.file_name = "added_file.txt"
|
resource.new_branch = false
|
||||||
push.file_content = "File Added"
|
resource.file_name = "added_file.txt"
|
||||||
|
resource.file_content = "File Added"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
|
@ -46,8 +52,10 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
|
target
|
||||||
|
source
|
||||||
project.visit!
|
project.visit!
|
||||||
Page::Project::Show.act { new_merge_request }
|
Page::Project::Show.perform(&:new_merge_request)
|
||||||
Page::MergeRequest::New.perform do |page|
|
Page::MergeRequest::New.perform do |page|
|
||||||
page.fill_title(@title)
|
page.fill_title(@title)
|
||||||
page.fill_description(@description)
|
page.fill_description(@description)
|
||||||
|
|
|
@ -4,19 +4,24 @@ module QA
|
||||||
class MergeRequestFromFork < MergeRequest
|
class MergeRequestFromFork < MergeRequest
|
||||||
attr_accessor :fork_branch
|
attr_accessor :fork_branch
|
||||||
|
|
||||||
dependency Factory::Resource::Fork, as: :fork
|
attribute :fork do
|
||||||
|
Factory::Resource::Fork.fabricate!
|
||||||
|
end
|
||||||
|
|
||||||
dependency Factory::Repository::ProjectPush, as: :push do |push, factory|
|
attribute :push do
|
||||||
push.project = factory.fork
|
Factory::Repository::ProjectPush.fabricate! do |resource|
|
||||||
push.branch_name = factory.fork_branch
|
resource.project = fork
|
||||||
push.file_name = 'file2.txt'
|
resource.branch_name = fork_branch
|
||||||
push.user = factory.fork.user
|
resource.file_name = 'file2.txt'
|
||||||
|
resource.user = fork.user
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
|
push
|
||||||
fork.visit!
|
fork.visit!
|
||||||
Page::Project::Show.act { new_merge_request }
|
Page::Project::Show.perform(&:new_merge_request)
|
||||||
Page::MergeRequest::New.act { create_merge_request }
|
Page::MergeRequest::New.perform(&:create_merge_request)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,13 +7,13 @@ module QA
|
||||||
class PersonalAccessToken < Factory::Base
|
class PersonalAccessToken < Factory::Base
|
||||||
attr_accessor :name
|
attr_accessor :name
|
||||||
|
|
||||||
product :access_token do
|
attribute :access_token do
|
||||||
Page::Profile::PersonalAccessTokens.act { created_access_token }
|
Page::Profile::PersonalAccessTokens.perform(&:created_access_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
Page::Main::Menu.act { go_to_profile_settings }
|
Page::Main::Menu.perform(&:go_to_profile_settings)
|
||||||
Page::Profile::Menu.act { click_access_tokens }
|
Page::Profile::Menu.perform(&:click_access_tokens)
|
||||||
|
|
||||||
Page::Profile::PersonalAccessTokens.perform do |page|
|
Page::Profile::PersonalAccessTokens.perform do |page|
|
||||||
page.fill_token_name(name || 'api-test-token')
|
page.fill_token_name(name || 'api-test-token')
|
||||||
|
|
|
@ -4,25 +4,24 @@ module QA
|
||||||
module Factory
|
module Factory
|
||||||
module Resource
|
module Resource
|
||||||
class Project < Factory::Base
|
class Project < Factory::Base
|
||||||
attr_accessor :description
|
attribute :name
|
||||||
attr_reader :name
|
attribute :description
|
||||||
|
|
||||||
dependency Factory::Resource::Group, as: :group
|
attribute :group do
|
||||||
|
Factory::Resource::Group.fabricate!
|
||||||
|
end
|
||||||
|
|
||||||
product :group
|
attribute :repository_ssh_location do
|
||||||
product :name
|
Page::Project::Show.perform do |page|
|
||||||
|
page.choose_repository_clone_ssh
|
||||||
product :repository_ssh_location do
|
page.repository_location
|
||||||
Page::Project::Show.act do
|
|
||||||
choose_repository_clone_ssh
|
|
||||||
repository_location
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
product :repository_http_location do
|
attribute :repository_http_location do
|
||||||
Page::Project::Show.act do
|
Page::Project::Show.perform do |page|
|
||||||
choose_repository_clone_http
|
page.choose_repository_clone_http
|
||||||
repository_location
|
page.repository_location
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ module QA
|
||||||
def fabricate!
|
def fabricate!
|
||||||
group.visit!
|
group.visit!
|
||||||
|
|
||||||
Page::Group::Show.act { go_to_new_project }
|
Page::Group::Show.perform(&:go_to_new_project)
|
||||||
|
|
||||||
Page::Project::New.perform do |page|
|
Page::Project::New.perform do |page|
|
||||||
page.choose_test_namespace
|
page.choose_test_namespace
|
||||||
|
|
|
@ -6,14 +6,16 @@ module QA
|
||||||
class ProjectImportedFromGithub < Resource::Project
|
class ProjectImportedFromGithub < Resource::Project
|
||||||
attr_writer :personal_access_token, :github_repository_path
|
attr_writer :personal_access_token, :github_repository_path
|
||||||
|
|
||||||
dependency Factory::Resource::Group, as: :group
|
attribute :group do
|
||||||
|
Factory::Resource::Group.fabricate!
|
||||||
|
end
|
||||||
|
|
||||||
product :name
|
attribute :name
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
group.visit!
|
group.visit!
|
||||||
|
|
||||||
Page::Group::Show.act { go_to_new_project }
|
Page::Group::Show.perform(&:go_to_new_project)
|
||||||
|
|
||||||
Page::Project::New.perform do |page|
|
Page::Project::New.perform do |page|
|
||||||
page.go_to_import_project
|
page.go_to_import_project
|
||||||
|
|
|
@ -3,11 +3,12 @@ module QA
|
||||||
module Resource
|
module Resource
|
||||||
class ProjectMilestone < Factory::Base
|
class ProjectMilestone < Factory::Base
|
||||||
attr_accessor :description
|
attr_accessor :description
|
||||||
attr_reader :title
|
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project
|
attribute :project do
|
||||||
|
Factory::Resource::Project.fabricate!
|
||||||
|
end
|
||||||
|
|
||||||
product :title
|
attribute :title
|
||||||
|
|
||||||
def title=(title)
|
def title=(title)
|
||||||
@title = "#{title}-#{SecureRandom.hex(4)}"
|
@title = "#{title}-#{SecureRandom.hex(4)}"
|
||||||
|
@ -17,12 +18,12 @@ module QA
|
||||||
def fabricate!
|
def fabricate!
|
||||||
project.visit!
|
project.visit!
|
||||||
|
|
||||||
Page::Project::Menu.act do
|
Page::Project::Menu.perform do |page|
|
||||||
click_issues
|
page.click_issues
|
||||||
click_milestones
|
page.click_milestones
|
||||||
end
|
end
|
||||||
|
|
||||||
Page::Project::Milestone::Index.act { click_new_milestone }
|
Page::Project::Milestone::Index.perform(&:click_new_milestone)
|
||||||
|
|
||||||
Page::Project::Milestone::New.perform do |milestone_new|
|
Page::Project::Milestone::New.perform do |milestone_new|
|
||||||
milestone_new.set_title(@title)
|
milestone_new.set_title(@title)
|
||||||
|
|
|
@ -6,9 +6,11 @@ module QA
|
||||||
class Runner < Factory::Base
|
class Runner < Factory::Base
|
||||||
attr_writer :name, :tags, :image
|
attr_writer :name, :tags, :image
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-with-ci-cd'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
project.description = 'Project with CI/CD Pipelines'
|
resource.name = 'project-with-ci-cd'
|
||||||
|
resource.description = 'Project with CI/CD Pipelines'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
|
@ -26,7 +28,7 @@ module QA
|
||||||
def fabricate!
|
def fabricate!
|
||||||
project.visit!
|
project.visit!
|
||||||
|
|
||||||
Page::Project::Menu.act { click_ci_cd_settings }
|
Page::Project::Menu.perform(&:click_ci_cd_settings)
|
||||||
|
|
||||||
Service::Runner.new(name).tap do |runner|
|
Service::Runner.new(name).tap do |runner|
|
||||||
Page::Project::Settings::CICD.perform do |settings|
|
Page::Project::Settings::CICD.perform do |settings|
|
||||||
|
|
|
@ -8,17 +8,15 @@ module QA
|
||||||
class Sandbox < Factory::Base
|
class Sandbox < Factory::Base
|
||||||
attr_reader :path
|
attr_reader :path
|
||||||
|
|
||||||
product :id do
|
attribute :id
|
||||||
true # We don't retrieve the Group ID when using the Browser UI
|
attribute :path
|
||||||
end
|
|
||||||
product :path
|
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@path = Runtime::Namespace.sandbox_name
|
@path = Runtime::Namespace.sandbox_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
Page::Main::Menu.act { go_to_groups }
|
Page::Main::Menu.perform(&:go_to_groups)
|
||||||
|
|
||||||
Page::Dashboard::Groups.perform do |page|
|
Page::Dashboard::Groups.perform do |page|
|
||||||
if page.has_group?(path)
|
if page.has_group?(path)
|
||||||
|
|
|
@ -4,15 +4,17 @@ module QA
|
||||||
class SecretVariable < Factory::Base
|
class SecretVariable < Factory::Base
|
||||||
attr_accessor :key, :value
|
attr_accessor :key, :value
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-with-secret-variables'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
project.description = 'project for adding secret variable test'
|
resource.name = 'project-with-secret-variables'
|
||||||
|
resource.description = 'project for adding secret variable test'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
project.visit!
|
project.visit!
|
||||||
|
|
||||||
Page::Project::Menu.act { click_ci_cd_settings }
|
Page::Project::Menu.perform(&:click_ci_cd_settings)
|
||||||
|
|
||||||
Page::Project::Settings::CICD.perform do |setting|
|
Page::Project::Settings::CICD.perform do |setting|
|
||||||
setting.expand_secret_variables do |page|
|
setting.expand_secret_variables do |page|
|
||||||
|
|
|
@ -6,21 +6,19 @@ module QA
|
||||||
class SSHKey < Factory::Base
|
class SSHKey < Factory::Base
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
|
||||||
attr_accessor :title
|
|
||||||
attr_reader :private_key, :public_key, :fingerprint
|
|
||||||
def_delegators :key, :private_key, :public_key, :fingerprint
|
def_delegators :key, :private_key, :public_key, :fingerprint
|
||||||
|
|
||||||
product :private_key
|
attribute :private_key
|
||||||
product :title
|
attribute :title
|
||||||
product :fingerprint
|
attribute :fingerprint
|
||||||
|
|
||||||
def key
|
def key
|
||||||
@key ||= Runtime::Key::RSA.new
|
@key ||= Runtime::Key::RSA.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
Page::Main::Menu.act { go_to_profile_settings }
|
Page::Main::Menu.perform(&:go_to_profile_settings)
|
||||||
Page::Profile::Menu.act { click_ssh_keys }
|
Page::Profile::Menu.perform(&:click_ssh_keys)
|
||||||
|
|
||||||
Page::Profile::SSHKeys.perform do |page|
|
Page::Profile::SSHKeys.perform do |page|
|
||||||
page.add_key(public_key, title)
|
page.add_key(public_key, title)
|
||||||
|
|
|
@ -5,7 +5,6 @@ module QA
|
||||||
module Resource
|
module Resource
|
||||||
class User < Factory::Base
|
class User < Factory::Base
|
||||||
attr_reader :unique_id
|
attr_reader :unique_id
|
||||||
attr_writer :username, :password, :name, :email
|
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@unique_id = SecureRandom.hex(8)
|
@unique_id = SecureRandom.hex(8)
|
||||||
|
@ -31,14 +30,14 @@ module QA
|
||||||
defined?(@username) && defined?(@password)
|
defined?(@username) && defined?(@password)
|
||||||
end
|
end
|
||||||
|
|
||||||
product :name
|
attribute :name
|
||||||
product :username
|
attribute :username
|
||||||
product :email
|
attribute :email
|
||||||
product :password
|
attribute :password
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
# Don't try to log-out if we're not logged-in
|
# Don't try to log-out if we're not logged-in
|
||||||
if Page::Main::Menu.act { has_personal_area?(wait: 0) }
|
if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
|
||||||
Page::Main::Menu.perform { |main| main.sign_out }
|
Page::Main::Menu.perform { |main| main.sign_out }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,11 @@ module QA
|
||||||
class Wiki < Factory::Base
|
class Wiki < Factory::Base
|
||||||
attr_accessor :title, :content, :message
|
attr_accessor :title, :content, :message
|
||||||
|
|
||||||
dependency Factory::Resource::Project, as: :project do |project|
|
attribute :project do
|
||||||
project.name = 'project-for-wikis'
|
Factory::Resource::Project.fabricate! do |resource|
|
||||||
project.description = 'project for adding wikis'
|
resource.name = 'project-for-wikis'
|
||||||
|
resource.description = 'project for adding wikis'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
|
|
|
@ -5,9 +5,9 @@ module QA
|
||||||
def fabricate!(*traits)
|
def fabricate!(*traits)
|
||||||
raise ArgumentError unless traits.include?(:enabled)
|
raise ArgumentError unless traits.include?(:enabled)
|
||||||
|
|
||||||
Page::Main::Login.act { sign_in_using_credentials }
|
Page::Main::Login.perform(&:sign_in_using_credentials)
|
||||||
Page::Main::Menu.act { go_to_admin_area }
|
Page::Main::Menu.perform(&:go_to_admin_area)
|
||||||
Page::Admin::Menu.act { go_to_repository_settings }
|
Page::Admin::Menu.perform(&:go_to_repository_settings)
|
||||||
|
|
||||||
Page::Admin::Settings::Repository.perform do |setting|
|
Page::Admin::Settings::Repository.perform do |setting|
|
||||||
setting.expand_repository_storage do |page|
|
setting.expand_repository_storage do |page|
|
||||||
|
@ -16,7 +16,7 @@ module QA
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
QA::Page::Main::Menu.act { sign_out }
|
QA::Page::Main::Menu.perform(&:sign_out)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ module QA
|
||||||
module Logger
|
module Logger
|
||||||
extend SingleForwardable
|
extend SingleForwardable
|
||||||
|
|
||||||
def_delegators :logger, :debug, :info, :error, :warn, :fatal, :unknown
|
def_delegators :logger, :debug, :info, :warn, :error, :fatal, :unknown
|
||||||
|
|
||||||
singleton_class.module_eval do
|
singleton_class.module_eval do
|
||||||
def logger
|
def logger
|
||||||
|
|
|
@ -49,11 +49,13 @@ module QA
|
||||||
cluster.install_prometheus = true
|
cluster.install_prometheus = true
|
||||||
cluster.install_runner = true
|
cluster.install_runner = true
|
||||||
end
|
end
|
||||||
|
kubernetes_cluster.populate(:ingress_ip)
|
||||||
|
|
||||||
project.visit!
|
project.visit!
|
||||||
Page::Project::Menu.act { click_ci_cd_settings }
|
Page::Project::Menu.act { click_ci_cd_settings }
|
||||||
Page::Project::Settings::CICD.perform do |p|
|
Page::Project::Settings::CICD.perform do |p|
|
||||||
p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
|
p.enable_auto_devops_with_domain(
|
||||||
|
"#{kubernetes_cluster.ingress_ip}.nip.io")
|
||||||
end
|
end
|
||||||
|
|
||||||
project.visit!
|
project.visit!
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe QA::Factory::Base do
|
||||||
before do
|
before do
|
||||||
allow(subject).to receive(:current_url).and_return(product_location)
|
allow(subject).to receive(:current_url).and_return(product_location)
|
||||||
allow(subject).to receive(:new).and_return(factory)
|
allow(subject).to receive(:new).and_return(factory)
|
||||||
allow(QA::Factory::Product).to receive(:populate!).with(factory, product_location).and_return(product)
|
allow(QA::Factory::Product).to receive(:new).with(factory).and_return(product)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -115,73 +115,134 @@ describe QA::Factory::Base do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.dependency' do
|
shared_context 'simple factory' do
|
||||||
let(:dependency) { spy('dependency') }
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_const('Some::MyDependency', dependency)
|
|
||||||
end
|
|
||||||
|
|
||||||
subject do
|
subject do
|
||||||
Class.new(described_class) do
|
Class.new(QA::Factory::Base) do
|
||||||
dependency Some::MyDependency, as: :mydep do |factory|
|
attribute :test do
|
||||||
factory.something!
|
'block'
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'appends a new dependency and accessors' do
|
attribute :no_block
|
||||||
expect(subject.dependencies).to be_one
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'defines dependency accessors' do
|
|
||||||
expect(subject.new).to respond_to :mydep, :mydep=
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'dependencies fabrication' do
|
|
||||||
let(:dependency) { double('dependency') }
|
|
||||||
let(:instance) { spy('instance') }
|
|
||||||
|
|
||||||
subject do
|
|
||||||
Class.new(described_class) do
|
|
||||||
dependency Some::MyDependency, as: :mydep
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_const('Some::MyDependency', dependency)
|
|
||||||
|
|
||||||
allow(subject).to receive(:new).and_return(instance)
|
|
||||||
allow(subject).to receive(:current_url).and_return(product_location)
|
|
||||||
allow(instance).to receive(:mydep).and_return(nil)
|
|
||||||
expect(QA::Factory::Product).to receive(:populate!)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'builds all dependencies first' do
|
|
||||||
expect(dependency).to receive(:fabricate!).once
|
|
||||||
|
|
||||||
subject.fabricate!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.product' do
|
|
||||||
include_context 'fabrication context'
|
|
||||||
|
|
||||||
subject do
|
|
||||||
Class.new(described_class) do
|
|
||||||
def fabricate!
|
def fabricate!
|
||||||
"any"
|
'any'
|
||||||
end
|
end
|
||||||
|
|
||||||
product :token
|
def self.current_url
|
||||||
|
'http://stub'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:factory) { subject.new }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.attribute' do
|
||||||
|
include_context 'simple factory'
|
||||||
|
|
||||||
it 'appends new product attribute' do
|
it 'appends new product attribute' do
|
||||||
expect(subject.attributes).to be_one
|
expect(subject.attributes_names).to eq([:no_block, :test, :web_url])
|
||||||
expect(subject.attributes[0]).to be_a(QA::Factory::Product::Attribute)
|
end
|
||||||
expect(subject.attributes[0].name).to eq(:token)
|
|
||||||
|
context 'when the product attribute is populated via a block' do
|
||||||
|
it 'returns a fabrication product and defines factory attributes as its methods' do
|
||||||
|
result = subject.fabricate!(factory: factory)
|
||||||
|
|
||||||
|
expect(result).to be_a(QA::Factory::Product)
|
||||||
|
expect(result.test).to eq('block')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the product attribute is populated via the api' do
|
||||||
|
let(:api_resource) { { no_block: 'api' } }
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(factory).to receive(:api_resource).and_return(api_resource)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a fabrication product and defines factory attributes as its methods' do
|
||||||
|
result = subject.fabricate!(factory: factory)
|
||||||
|
|
||||||
|
expect(result).to be_a(QA::Factory::Product)
|
||||||
|
expect(result.no_block).to eq('api')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the attribute also has a block in the factory' do
|
||||||
|
let(:api_resource) { { test: 'api_with_block' } }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(QA::Runtime::Logger).to receive(:info)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the api value and emits an INFO log entry' do
|
||||||
|
result = subject.fabricate!(factory: factory)
|
||||||
|
|
||||||
|
expect(result).to be_a(QA::Factory::Product)
|
||||||
|
expect(result.test).to eq('api_with_block')
|
||||||
|
expect(QA::Runtime::Logger)
|
||||||
|
.to have_received(:info).with(/api_with_block/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the product attribute is populated via a factory attribute' do
|
||||||
|
before do
|
||||||
|
factory.test = 'value'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a fabrication product and defines factory attributes as its methods' do
|
||||||
|
result = subject.fabricate!(factory: factory)
|
||||||
|
|
||||||
|
expect(result).to be_a(QA::Factory::Product)
|
||||||
|
expect(result.test).to eq('value')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the api also has such response' do
|
||||||
|
before do
|
||||||
|
allow(factory).to receive(:api_resource).and_return({ test: 'api' })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the factory attribute for the product' do
|
||||||
|
result = subject.fabricate!(factory: factory)
|
||||||
|
|
||||||
|
expect(result).to be_a(QA::Factory::Product)
|
||||||
|
expect(result.test).to eq('value')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the product attribute has no value' do
|
||||||
|
it 'raises an error because no values could be found' do
|
||||||
|
result = subject.fabricate!(factory: factory)
|
||||||
|
|
||||||
|
expect { result.no_block }
|
||||||
|
.to raise_error(described_class::NoValueError, "No value was computed for product no_block of factory #{factory.class.name}.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#web_url' do
|
||||||
|
include_context 'simple factory'
|
||||||
|
|
||||||
|
it 'sets #web_url to #current_url after fabrication' do
|
||||||
|
subject.fabricate!(factory: factory)
|
||||||
|
|
||||||
|
expect(factory.web_url).to eq(subject.current_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#visit!' do
|
||||||
|
include_context 'simple factory'
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(factory).to receive(:visit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls #visit with the underlying #web_url' do
|
||||||
|
factory.web_url = subject.current_url
|
||||||
|
factory.visit!
|
||||||
|
|
||||||
|
expect(factory).to have_received(:visit).with(subject.current_url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
describe QA::Factory::Dependency do
|
|
||||||
let(:dependency) { spy('dependency' ) }
|
|
||||||
let(:factory) { spy('factory') }
|
|
||||||
let(:block) { spy('block') }
|
|
||||||
|
|
||||||
let(:signature) do
|
|
||||||
double('signature', name: :mydep, factory: dependency, block: block)
|
|
||||||
end
|
|
||||||
|
|
||||||
subject do
|
|
||||||
described_class.new(factory, signature)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#overridden?' do
|
|
||||||
it 'returns true if factory has overridden dependency' do
|
|
||||||
allow(factory).to receive(:mydep).and_return('something')
|
|
||||||
|
|
||||||
expect(subject).to be_overridden
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns false if dependency has not been overridden' do
|
|
||||||
allow(factory).to receive(:mydep).and_return(nil)
|
|
||||||
|
|
||||||
expect(subject).not_to be_overridden
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#build!' do
|
|
||||||
context 'when dependency has been overridden' do
|
|
||||||
before do
|
|
||||||
allow(subject).to receive(:overridden?).and_return(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not fabricate dependency' do
|
|
||||||
subject.build!
|
|
||||||
|
|
||||||
expect(dependency).not_to have_received(:fabricate!)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when dependency has not been overridden' do
|
|
||||||
before do
|
|
||||||
allow(subject).to receive(:overridden?).and_return(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'fabricates dependency' do
|
|
||||||
subject.build!
|
|
||||||
|
|
||||||
expect(dependency).to have_received(:fabricate!)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sets product in the factory' do
|
|
||||||
subject.build!
|
|
||||||
|
|
||||||
expect(factory).to have_received(:mydep=).with(dependency)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'calls given block with dependency factory and caller factory' do
|
|
||||||
expect(dependency).to receive(:fabricate!).and_yield(dependency)
|
|
||||||
|
|
||||||
subject.build!
|
|
||||||
|
|
||||||
expect(block).to have_received(:call).with(dependency, factory)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with no block given' do
|
|
||||||
let(:signature) do
|
|
||||||
double('signature', name: :mydep, factory: dependency, block: nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not error' do
|
|
||||||
subject.build!
|
|
||||||
|
|
||||||
expect(dependency).to have_received(:fabricate!)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,73 +1,21 @@
|
||||||
describe QA::Factory::Product do
|
describe QA::Factory::Product do
|
||||||
let(:factory) do
|
let(:factory) do
|
||||||
Class.new(QA::Factory::Base) do
|
Class.new(QA::Factory::Base) do
|
||||||
def foo
|
attribute :test do
|
||||||
'bar'
|
'block'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attribute :no_block
|
||||||
end.new
|
end.new
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:product) { spy('product') }
|
let(:product) { spy('product') }
|
||||||
let(:product_location) { 'http://product_location' }
|
let(:product_location) { 'http://product_location' }
|
||||||
|
|
||||||
subject { described_class.new(factory, product_location) }
|
subject { described_class.new(factory) }
|
||||||
|
|
||||||
describe '.populate!' do
|
|
||||||
before do
|
before do
|
||||||
expect(factory.class).to receive(:attributes).and_return(attributes)
|
factory.web_url = product_location
|
||||||
end
|
|
||||||
|
|
||||||
context 'when the product attribute is populated via a block' do
|
|
||||||
let(:attributes) do
|
|
||||||
[QA::Factory::Product::Attribute.new(:test, proc { 'returned' })]
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a fabrication product and defines factory attributes as its methods' do
|
|
||||||
result = described_class.populate!(factory, product_location)
|
|
||||||
|
|
||||||
expect(result).to be_a(described_class)
|
|
||||||
expect(result.test).to eq('returned')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when the product attribute is populated via the api' do
|
|
||||||
let(:attributes) do
|
|
||||||
[QA::Factory::Product::Attribute.new(:test)]
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a fabrication product and defines factory attributes as its methods' do
|
|
||||||
expect(factory).to receive(:api_resource).and_return({ test: 'returned' })
|
|
||||||
|
|
||||||
result = described_class.populate!(factory, product_location)
|
|
||||||
|
|
||||||
expect(result).to be_a(described_class)
|
|
||||||
expect(result.test).to eq('returned')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when the product attribute is populated via a factory attribute' do
|
|
||||||
let(:attributes) do
|
|
||||||
[QA::Factory::Product::Attribute.new(:foo)]
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a fabrication product and defines factory attributes as its methods' do
|
|
||||||
result = described_class.populate!(factory, product_location)
|
|
||||||
|
|
||||||
expect(result).to be_a(described_class)
|
|
||||||
expect(result.foo).to eq('bar')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when the product attribute has no value' do
|
|
||||||
let(:attributes) do
|
|
||||||
[QA::Factory::Product::Attribute.new(:bar)]
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a fabrication product and defines factory attributes as its methods' do
|
|
||||||
expect { described_class.populate!(factory, product_location) }
|
|
||||||
.to raise_error(described_class::NoValueError, "No value was computed for product bar of factory #{factory.class.name}.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.visit!' do
|
describe '.visit!' do
|
||||||
|
|
Loading…
Reference in a new issue