Commit 0165d36f authored by Sanad Liaquat's avatar Sanad Liaquat

Merge branch 'acunskis-iteration-migration' into 'master'

E2E: Validate iterations migration

See merge request gitlab-org/gitlab!70785
parents 89d2d950 8b325d3b
...@@ -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
# Iteration attributes
#
# @return [String]
def gql_attributes
@gql_attributes ||= <<~GQL
id
iid
description
title
state
startDate
dueDate
createdAt
updatedAt
webUrl
GQL
end end
# Path for fetching iteration
#
# @return [String]
def api_get_path def api_get_path
"gid://gitlab/Iteration/#{id}" "/graphql"
end 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
# 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