285 lines
10 KiB
Ruby
285 lines
10 KiB
Ruby
# frozen_string_literal: true
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe DesignManagement::CopyDesignCollection::CopyService, :clean_gitlab_redis_shared_state do
|
|
include DesignManagementTestHelpers
|
|
|
|
let_it_be(:user) { create(:user) }
|
|
let_it_be(:project) { create(:project) }
|
|
let_it_be(:issue, refind: true) { create(:issue, project: project) }
|
|
let(:target_issue) { create(:issue) }
|
|
|
|
subject { described_class.new(project, user, issue: issue, target_issue: target_issue).execute }
|
|
|
|
before do
|
|
enable_design_management
|
|
end
|
|
|
|
shared_examples 'service error' do |message:|
|
|
it 'returns an error response', :aggregate_failures do
|
|
expect(subject).to be_kind_of(ServiceResponse)
|
|
expect(subject).to be_error
|
|
expect(subject.message).to eq(message)
|
|
end
|
|
end
|
|
|
|
shared_examples 'service success' do
|
|
it 'returns a success response', :aggregate_failures do
|
|
expect(subject).to be_kind_of(ServiceResponse)
|
|
expect(subject).to be_success
|
|
end
|
|
end
|
|
|
|
include_examples 'service error', message: 'User cannot copy design collection to issue'
|
|
|
|
context 'when user has permission to read the design collection' do
|
|
before_all do
|
|
project.add_reporter(user)
|
|
end
|
|
|
|
include_examples 'service error', message: 'User cannot copy design collection to issue'
|
|
|
|
context 'when the user also has permission to admin the target issue' do
|
|
let(:target_repository) { target_issue.project.design_repository }
|
|
|
|
before do
|
|
target_issue.project.add_reporter(user)
|
|
end
|
|
|
|
include_examples 'service error', message: 'Target design collection must first be queued'
|
|
|
|
context 'when the target design collection has been queued' do
|
|
before do
|
|
target_issue.design_collection.start_copy!
|
|
end
|
|
|
|
include_examples 'service error', message: 'Design collection has no designs'
|
|
|
|
context 'when design collection has designs' do
|
|
let_it_be(:designs) do
|
|
create_list(:design, 3, :with_lfs_file, :with_relative_position, issue: issue, project: project)
|
|
end
|
|
|
|
context 'when target issue already has designs' do
|
|
before do
|
|
create(:design, issue: target_issue, project: target_issue.project)
|
|
end
|
|
|
|
include_examples 'service error', message: 'Target design collection already has designs'
|
|
end
|
|
|
|
context 'when target project already has designs' do
|
|
let!(:issue_x) { create(:issue, project: target_issue.project) }
|
|
let!(:existing) { create(:design, issue: issue_x, project: target_issue.project) }
|
|
|
|
let(:new_designs) do
|
|
target_issue.reset
|
|
target_issue.designs.where.not(id: existing.id)
|
|
end
|
|
|
|
it 'sets IIDs for new designs above existing ones' do
|
|
subject
|
|
|
|
expect(new_designs).to all(have_attributes(iid: (be > existing.iid)))
|
|
end
|
|
|
|
it 'does not allow for IID collisions' do
|
|
subject
|
|
create(:design, issue: issue_x, project: target_issue.project)
|
|
|
|
design_iids = target_issue.project.designs.map(&:id)
|
|
|
|
expect(design_iids).to match_array(design_iids.uniq)
|
|
end
|
|
end
|
|
|
|
include_examples 'service success'
|
|
|
|
it 'creates a design repository for the target project' do
|
|
expect { subject }.to change { target_repository.exists? }.from(false).to(true)
|
|
end
|
|
|
|
context 'when the target project already has a design repository' do
|
|
before do
|
|
target_repository.create_if_not_exists
|
|
end
|
|
|
|
include_examples 'service success'
|
|
end
|
|
|
|
it 'copies the designs correctly', :aggregate_failures do
|
|
expect { subject }.to change { target_issue.designs.count }.by(3)
|
|
|
|
old_designs = issue.designs.ordered
|
|
new_designs = target_issue.designs.ordered
|
|
|
|
new_designs.zip(old_designs).each do |new_design, old_design|
|
|
expect(new_design).to have_attributes(
|
|
filename: old_design.filename,
|
|
relative_position: old_design.relative_position,
|
|
issue: target_issue,
|
|
project: target_issue.project
|
|
)
|
|
end
|
|
end
|
|
|
|
it 'copies the design versions correctly', :aggregate_failures do
|
|
expect { subject }.to change { target_issue.design_versions.count }.by(3)
|
|
|
|
old_versions = issue.design_versions.ordered
|
|
new_versions = target_issue.design_versions.ordered
|
|
|
|
new_versions.zip(old_versions).each do |new_version, old_version|
|
|
expect(new_version).to have_attributes(
|
|
created_at: old_version.created_at,
|
|
author_id: old_version.author_id
|
|
)
|
|
expect(new_version.designs.pluck(:filename)).to eq(old_version.designs.pluck(:filename))
|
|
expect(new_version.actions.pluck(:event)).to eq(old_version.actions.pluck(:event))
|
|
end
|
|
end
|
|
|
|
it 'copies the design actions correctly', :aggregate_failures do
|
|
expect { subject }.to change { DesignManagement::Action.count }.by(3)
|
|
|
|
old_actions = issue.design_versions.ordered.flat_map(&:actions)
|
|
new_actions = target_issue.design_versions.ordered.flat_map(&:actions)
|
|
|
|
new_actions.zip(old_actions).each do |new_action, old_action|
|
|
# This is a way to identify if the versions linked to the actions
|
|
# are correct is to compare design filenames, as the SHA changes.
|
|
new_design_filenames = new_action.version.designs.ordered.pluck(:filename)
|
|
old_design_filenames = old_action.version.designs.ordered.pluck(:filename)
|
|
|
|
expect(new_design_filenames).to eq(old_design_filenames)
|
|
expect(new_action.event).to eq(old_action.event)
|
|
expect(new_action.design.filename).to eq(old_action.design.filename)
|
|
end
|
|
end
|
|
|
|
it 'copies design notes correctly', :aggregate_failures, :sidekiq_inline do
|
|
old_notes = [
|
|
create(:diff_note_on_design, note: 'first note', noteable: designs.first, project: project, author: create(:user)),
|
|
create(:diff_note_on_design, note: 'second note', noteable: designs.first, project: project, author: create(:user))
|
|
]
|
|
matchers = old_notes.map do |note|
|
|
have_attributes(
|
|
note.attributes.slice(
|
|
:type,
|
|
:author_id,
|
|
:note,
|
|
:position
|
|
)
|
|
)
|
|
end
|
|
|
|
expect { subject }.to change { Note.count }.by(2)
|
|
|
|
new_notes = target_issue.designs.first.notes.fresh
|
|
|
|
expect(new_notes).to match_array(matchers)
|
|
end
|
|
|
|
it 'links the LfsObjects' do
|
|
expect { subject }.to change { target_issue.project.lfs_objects.count }.by(3)
|
|
end
|
|
|
|
it 'copies the Git repository data', :aggregate_failures do
|
|
subject
|
|
|
|
expect(commits_on_master(limit: 99)).to include(*target_issue.design_versions.ordered.pluck(:sha))
|
|
end
|
|
|
|
it 'creates a master branch if none previously existed' do
|
|
expect { subject }.to change { target_repository.branch_names }.from([]).to(['master'])
|
|
end
|
|
|
|
it 'leaves the design collection in the correct copy state' do
|
|
subject
|
|
|
|
expect(target_issue.design_collection).to be_copy_ready
|
|
end
|
|
|
|
describe 'rollback' do
|
|
before do
|
|
# Ensure the very last step throws an error
|
|
expect_next_instance_of(described_class) do |service|
|
|
expect(service).to receive(:finalize!).and_raise
|
|
end
|
|
end
|
|
|
|
include_examples 'service error', message: 'Designs were unable to be copied successfully'
|
|
|
|
it 'rollsback all PostgreSQL data created', :aggregate_failures do
|
|
expect { subject }.not_to change {
|
|
[
|
|
DesignManagement::Design.count,
|
|
DesignManagement::Action.count,
|
|
DesignManagement::Version.count,
|
|
Note.count
|
|
]
|
|
}
|
|
|
|
collections = [
|
|
target_issue.design_collection,
|
|
target_issue.designs,
|
|
target_issue.design_versions
|
|
]
|
|
|
|
expect(collections).to all(be_empty)
|
|
end
|
|
|
|
it 'does not alter master branch', :aggregate_failures do
|
|
# Add some Git data to the target_repository, so we are testing
|
|
# that any original data remains
|
|
issue_2 = create(:issue, project: target_issue.project)
|
|
create(:design, :with_file, issue: issue_2, project: target_issue.project)
|
|
|
|
expect { subject }.not_to change { commits_on_master }
|
|
end
|
|
|
|
it 'sets the design collection copy state' do
|
|
subject
|
|
|
|
expect(target_issue.design_collection).to be_copy_error
|
|
end
|
|
end
|
|
|
|
def commits_on_master(limit: 10)
|
|
target_repository.commits('master', limit: limit).map(&:id)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'Alert if schema changes', :aggregate_failures do
|
|
let_it_be(:config_file) { Rails.root.join('lib/gitlab/design_management/copy_design_collection_model_attributes.yml') }
|
|
let_it_be(:config) { YAML.load_file(config_file).symbolize_keys }
|
|
|
|
%w(Design Action Version).each do |model|
|
|
specify do
|
|
attributes = config["#{model.downcase}_attributes".to_sym] || []
|
|
ignored_attributes = config["ignore_#{model.downcase}_attributes".to_sym]
|
|
|
|
expect(attributes + ignored_attributes).to contain_exactly(
|
|
*DesignManagement.const_get(model, false).column_names
|
|
), failure_message(model)
|
|
end
|
|
end
|
|
|
|
def failure_message(model)
|
|
<<-MSG
|
|
The schema of the `#{model}` model has changed.
|
|
|
|
`#{described_class.name}` refers to specific lists of attributes of `#{model}` to either
|
|
copy or ignore, so that we continue to copy designs correctly after schema changes.
|
|
|
|
Please update:
|
|
#{config_file}
|
|
to reflect the latest changes to `#{model}`. See that file for more information.
|
|
MSG
|
|
end
|
|
end
|
|
end
|