Commit 8b325d3b authored by Andrejs Cunskis's avatar Andrejs Cunskis

Validate iterations migration

Add Iteration migration validation

Improve ApiFabricator to allow usage of graphql query

Minor code formatting improvements
parent 6176f4e8
...@@ -20,9 +20,29 @@ module QA ...@@ -20,9 +20,29 @@ module QA
end end
end end
# Get group iterations
#
# @return [Array<QA::EE::Resource::GroupIteration>]
def iterations
parse_body(api_get_from(api_iterations_path)).map do |iteration|
GroupIteration.init do |resource|
resource.group = self
resource.api_client = api_client
resource.id = iteration[:id]
resource.iid = iteration[:iid]
resource.title = iteration[:title]
resource.description = iteration[:description]
end
end
end
def api_epics_path def api_epics_path
"#{api_get_path}/epics" "#{api_get_path}/epics"
end end
def api_iterations_path
"#{api_get_path}/iterations"
end
end end
end end
end end
......
...@@ -6,19 +6,21 @@ module QA ...@@ -6,19 +6,21 @@ module QA
class GroupIteration < QA::Resource::Base class GroupIteration < QA::Resource::Base
include Support::Dates include Support::Dates
attr_accessor :title
attribute :group do attribute :group do
QA::Resource::Group.fabricate_via_api! do |group| QA::Resource::Group.fabricate_via_api! do |group|
group.path = "group-to-test-iterations-#{SecureRandom.hex(8)}" group.path = "group-to-test-iterations-#{SecureRandom.hex(8)}"
end end
end end
attribute :id attributes :id,
attribute :start_date :iid,
attribute :due_date :description,
attribute :description :title,
attribute :title :state,
:start_date,
:due_date,
:created_at,
:updated_at
def initialize def initialize
@start_date = current_date_yyyy_mm_dd @start_date = current_date_yyyy_mm_dd
...@@ -34,23 +36,68 @@ module QA ...@@ -34,23 +36,68 @@ module QA
QA::EE::Page::Group::Iteration::Index.perform(&:click_new_iteration_button) QA::EE::Page::Group::Iteration::Index.perform(&:click_new_iteration_button)
QA::EE::Page::Group::Iteration::New.perform do |new| QA::EE::Page::Group::Iteration::New.perform do |iteration_page|
new.fill_title(@title) iteration_page.fill_title(@title)
new.fill_description(@description) iteration_page.fill_description(@description)
new.fill_start_date(@start_date) iteration_page.fill_start_date(@start_date)
new.fill_due_date(@due_date) iteration_page.fill_due_date(@due_date)
new.click_create_iteration_button iteration_page.click_create_iteration_button
end end
end end
# Iteration attributes
#
# @return [String]
def gql_attributes
@gql_attributes ||= <<~GQL
id
iid
description
title
state
startDate
dueDate
createdAt
updatedAt
webUrl
GQL
end
# Path for fetching iteration
#
# @return [String]
def api_get_path def api_get_path
"gid://gitlab/Iteration/#{id}" "/graphql"
end
# Fetch iteration
#
# @return [Hash]
def api_get
process_api_response(
api_post_to(
api_get_path,
<<~GQL
query {
iteration(id: "gid://gitlab/Iteration/#{id}") {
#{gql_attributes}
}
}
GQL
)
)
end end
# Path to create iteration
#
# @return [String]
def api_post_path def api_post_path
"/graphql" "/graphql"
end end
# Graphql mutation for iteration creation
#
# @return [String]
def api_post_body def api_post_body
<<~GQL <<~GQL
mutation { mutation {
...@@ -62,18 +109,45 @@ module QA ...@@ -62,18 +109,45 @@ module QA
dueDate: "#{@due_date}" dueDate: "#{@due_date}"
}) { }) {
iteration { iteration {
id #{gql_attributes}
title
description
startDate
dueDate
webUrl
} }
errors errors
} }
} }
GQL GQL
end end
# Object comparison
#
# @param [QA::EE::Resource::GroupIteration] other
# @return [Boolean]
def ==(other)
other.is_a?(GroupIteration) && comparable_iteration == other.comparable_iteration
end
# Override inspect for a better rspec failure diff output
#
# @return [String]
def inspect
JSON.pretty_generate(comparable_iteration)
end
protected
# Return subset of fields for comparing iterations
#
# @return [Hash]
def comparable_iteration
reload! unless api_response
api_response.slice(
:title,
:description,
:state,
:due_date,
:start_date
)
end
end end
end end
end end
......
...@@ -96,31 +96,35 @@ module QA ...@@ -96,31 +96,35 @@ module QA
end end
def api_post def api_post
if api_post_path == "/graphql" process_api_response(api_post_to(api_post_path, api_post_body))
graphql_response = post( end
Runtime::API::Request.new(api_client, api_post_path).url,
query: api_post_body) def api_post_to(post_path, post_body)
if post_path == "/graphql"
graphql_response = post(Runtime::API::Request.new(api_client, post_path).url, query: post_body)
flattened_response = flatten_hash(parse_body(graphql_response)) body = flatten_hash(parse_body(graphql_response))
unless graphql_response.code == HTTP_STATUS_OK && flattened_response[:errors].empty? unless graphql_response.code == HTTP_STATUS_OK && (body[:errors].nil? || body[:errors].empty?)
raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{graphql_response.code}) with `#{graphql_response}`." raise(ResourceFabricationFailedError, <<~MSG)
Fabrication of #{self.class.name} using the API failed (#{graphql_response.code}) with `#{graphql_response}`.
MSG
end end
flattened_response[:web_url] = flattened_response.delete(:webUrl) body[:id] = body.fetch(:id).split('/').last
flattened_response[:id] = flattened_response.fetch(:id).split('/')[-1]
process_api_response(flattened_response) body.transform_keys { |key| key.to_s.underscore.to_sym }
else else
response = post( response = post(Runtime::API::Request.new(api_client, post_path).url, post_body)
Runtime::API::Request.new(api_client, api_post_path).url,
api_post_body)
unless response.code == HTTP_STATUS_CREATED unless response.code == HTTP_STATUS_CREATED
raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`." raise(
ResourceFabricationFailedError,
"Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
)
end end
process_api_response(parse_body(response)) parse_body(response)
end end
end end
......
...@@ -6,6 +6,7 @@ module QA ...@@ -6,6 +6,7 @@ module QA
describe 'Bulk group import' do describe 'Bulk group import' do
let(:admin_api_client) { Runtime::API::Client.as_admin } let(:admin_api_client) { Runtime::API::Client.as_admin }
let(:api_client) { Runtime::API::Client.new(user: user) } let(:api_client) { Runtime::API::Client.new(user: user) }
# validate different epic author is migrated correctly
let(:author_api_client) { Runtime::API::Client.new(user: author) } let(:author_api_client) { Runtime::API::Client.new(user: author) }
let(:user) do let(:user) do
...@@ -46,6 +47,13 @@ module QA ...@@ -46,6 +47,13 @@ module QA
let(:source_epics) { source_group.epics } let(:source_epics) { source_group.epics }
let(:imported_epics) { imported_group.epics } let(:imported_epics) { imported_group.epics }
let(:source_iteration) do
EE::Resource::GroupIteration.fabricate_via_api! do |iteration|
iteration.api_client = api_client
iteration.group = source_group
end
end
# Find epic by title # Find epic by title
# #
# @param [Array] epics # @param [Array] epics
...@@ -76,6 +84,8 @@ module QA ...@@ -76,6 +84,8 @@ module QA
child_epic.award_emoji('thumbsup') child_epic.award_emoji('thumbsup')
child_epic.award_emoji('thumbsdown') child_epic.award_emoji('thumbsdown')
source_iteration
end end
after do after do
...@@ -83,7 +93,10 @@ module QA ...@@ -83,7 +93,10 @@ module QA
author.remove_via_api! author.remove_via_api!
end end
it 'imports group epics', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1921' do it(
'imports group epics and iterations',
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1921'
) do
expect { imported_group.import_status }.to( expect { imported_group.import_status }.to(
eventually_eq('finished').within(max_duration: 300, sleep_interval: 2) eventually_eq('finished').within(max_duration: 300, sleep_interval: 2)
) )
...@@ -91,11 +104,17 @@ module QA ...@@ -91,11 +104,17 @@ module QA
source_parent_epic = find_epic(source_epics, 'Parent epic') source_parent_epic = find_epic(source_epics, 'Parent epic')
imported_parent_epic = find_epic(imported_epics, 'Parent epic') imported_parent_epic = find_epic(imported_epics, 'Parent epic')
imported_child_epic = find_epic(imported_epics, 'Child epic') imported_child_epic = find_epic(imported_epics, 'Child epic')
imported_iteration = imported_group.reload!.iterations.find { |ml| ml.title == source_iteration.title }
aggregate_failures 'epics imported incorrectly' do aggregate_failures do
expect(imported_epics).to eq(source_epics) expect(imported_epics).to eq(source_epics)
expect(imported_child_epic.parent_id).to eq(imported_parent_epic.id) expect(imported_child_epic.parent_id).to eq(imported_parent_epic.id)
expect(imported_parent_epic.author).to eq(source_parent_epic.author) expect(imported_parent_epic.author).to eq(source_parent_epic.author)
expect(imported_iteration).to eq(source_iteration)
expect(imported_iteration.iid).to eq(source_iteration.iid)
expect(imported_iteration.created_at).to eq(source_iteration.created_at)
expect(imported_iteration.updated_at).to eq(source_iteration.updated_at)
end end
end end
end end
......
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