diff options
Diffstat (limited to 'spec')
15 files changed, 532 insertions, 183 deletions
diff --git a/spec/factories/group_import_states.rb b/spec/factories/group_import_states.rb index 0b491d444fa..47d4b480b12 100644 --- a/spec/factories/group_import_states.rb +++ b/spec/factories/group_import_states.rb @@ -3,6 +3,7 @@ FactoryBot.define do factory :group_import_state, class: 'GroupImportState', traits: %i[created] do association :group, factory: :group + association :user, factory: :user trait :created do status { 0 } diff --git a/spec/frontend/issuable_create/components/issuable_form_spec.js b/spec/frontend/issuable_create/components/issuable_form_spec.js index e2c6b4d9521..e489d1dae3e 100644 --- a/spec/frontend/issuable_create/components/issuable_form_spec.js +++ b/spec/frontend/issuable_create/components/issuable_form_spec.js @@ -79,6 +79,7 @@ describe('IssuableForm', () => { markdownDocsPath: wrapper.vm.descriptionHelpPath, addSpacingClasses: false, showSuggestPopover: true, + textareaValue: '', }); expect(descriptionFieldEl.find('textarea').exists()).toBe(true); expect(descriptionFieldEl.find('textarea').attributes('placeholder')).toBe( diff --git a/spec/frontend/lib/utils/axios_startup_calls_spec.js b/spec/frontend/lib/utils/axios_startup_calls_spec.js index e804cae7914..e12bf725560 100644 --- a/spec/frontend/lib/utils/axios_startup_calls_spec.js +++ b/spec/frontend/lib/utils/axios_startup_calls_spec.js @@ -111,21 +111,44 @@ describe('setupAxiosStartupCalls', () => { }); }); - it('removes GitLab Base URL from startup call', async () => { - const oldGon = window.gon; - window.gon = { gitlab_url: 'https://example.org/gitlab' }; - - window.gl.startup_calls = { - '/startup': { - fetchCall: mockFetchCall(200), - }, - }; - setupAxiosStartupCalls(axios); + describe('startup call', () => { + let oldGon; + + beforeEach(() => { + oldGon = window.gon; + window.gon = { gitlab_url: 'https://example.org/gitlab' }; + }); + + afterEach(() => { + window.gon = oldGon; + }); - const { data } = await axios.get('https://example.org/gitlab/startup'); + it('removes GitLab Base URL from startup call', async () => { + window.gl.startup_calls = { + '/startup': { + fetchCall: mockFetchCall(200), + }, + }; + setupAxiosStartupCalls(axios); - expect(data).toEqual(STARTUP_JS_RESPONSE); + const { data } = await axios.get('https://example.org/gitlab/startup'); - window.gon = oldGon; + expect(data).toEqual(STARTUP_JS_RESPONSE); + }); + + it('sorts the params in the requested API url', async () => { + window.gl.startup_calls = { + '/startup?alpha=true&bravo=true': { + fetchCall: mockFetchCall(200), + }, + }; + setupAxiosStartupCalls(axios); + + // Use a full url instead of passing options = { params: { ... } } to axios.get + // to ensure the params are listed in the specified order. + const { data } = await axios.get('https://example.org/gitlab/startup?bravo=true&alpha=true'); + + expect(data).toEqual(STARTUP_JS_RESPONSE); + }); }); }); diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js index 3da0a35f05a..f1ead33ec68 100644 --- a/spec/frontend/vue_shared/components/markdown/field_spec.js +++ b/spec/frontend/vue_shared/components/markdown/field_spec.js @@ -7,6 +7,7 @@ import axios from '~/lib/utils/axios_utils'; const markdownPreviewPath = `${TEST_HOST}/preview`; const markdownDocsPath = `${TEST_HOST}/docs`; +const textareaValue = 'testing\n123'; function assertMarkdownTabs(isWrite, writeLink, previewLink, wrapper) { expect(writeLink.element.parentNode.classList.contains('active')).toBe(isWrite); @@ -20,23 +21,11 @@ function createComponent() { markdownDocsPath, markdownPreviewPath, isSubmitting: false, + textareaValue, }, slots: { - textarea: '<textarea>testing\n123</textarea>', + textarea: `<textarea>${textareaValue}</textarea>`, }, - template: ` - <field-component - markdown-preview-path="${markdownPreviewPath}" - markdown-docs-path="${markdownDocsPath}" - :isSubmitting="false" - > - <textarea - slot="textarea" - v-model="text"> - <slot>this is a test</slot> - </textarea> - </field-component> - `, }); return wrapper; } diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb index b471e17e2e7..a1a8fdecb85 100644 --- a/spec/lib/gitlab/danger/roulette_spec.rb +++ b/spec/lib/gitlab/danger/roulette_spec.rb @@ -67,14 +67,18 @@ RSpec.describe Gitlab::Danger::Roulette do ) end - let(:teammate_json) do + let(:teammates) do [ backend_maintainer.to_h, frontend_maintainer.to_h, frontend_reviewer.to_h, software_engineer_in_test.to_h, engineering_productivity_reviewer.to_h - ].to_json + ] + end + + let(:teammate_json) do + teammates.to_json end subject(:roulette) { Object.new.extend(described_class) } @@ -210,6 +214,69 @@ RSpec.describe Gitlab::Danger::Roulette do end end end + + describe 'reviewer suggestion probability' do + let(:reviewer) { teammate_with_capability('reviewer', 'reviewer backend') } + let(:hungry_reviewer) { teammate_with_capability('hungry_reviewer', 'reviewer backend', hungry: true) } + let(:traintainer) { teammate_with_capability('traintainer', 'trainee_maintainer backend') } + let(:hungry_traintainer) { teammate_with_capability('hungry_traintainer', 'trainee_maintainer backend', hungry: true) } + let(:teammates) do + [ + reviewer.to_h, + hungry_reviewer.to_h, + traintainer.to_h, + hungry_traintainer.to_h + ] + end + + let(:categories) { [:backend] } + + # This test is testing probability with inherent randomness. + # The variance is inversely related to sample size + # Given large enough sample size, the variance would be smaller, + # but the test would take longer. + # Given smaller sample size, the variance would be larger, + # but the test would take less time. + let!(:sample_size) { 500 } + let!(:variance) { 0.1 } + + before do + # This test needs actual randomness to simulate probabilities + allow(subject).to receive(:new_random).and_return(Random.new) + WebMock + .stub_request(:get, described_class::ROULETTE_DATA_URL) + .to_return(body: teammate_json) + end + + it 'has 1:2:3:4 probability of picking reviewer, hungry_reviewer, traintainer, hungry_traintainer' do + picks = Array.new(sample_size).map do + spins = subject.spin(project, categories, timezone_experiment: timezone_experiment) + spins.first.reviewer.name + end + + expect(probability(picks, 'reviewer')).to be_within(variance).of(0.1) + expect(probability(picks, 'hungry_reviewer')).to be_within(variance).of(0.2) + expect(probability(picks, 'traintainer')).to be_within(variance).of(0.3) + expect(probability(picks, 'hungry_traintainer')).to be_within(variance).of(0.4) + end + + def probability(picks, role) + picks.count(role).to_f / picks.length + end + + def teammate_with_capability(name, capability, hungry: false) + Gitlab::Danger::Teammate.new( + { + 'name' => name, + 'projects' => { + 'gitlab' => capability + }, + 'available' => true, + 'hungry' => hungry + } + ) + end + end end RSpec::Matchers.define :match_teammates do |expected| diff --git a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb index eb9a3fa9bd8..6b2f80cc80a 100644 --- a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb @@ -5,16 +5,19 @@ require 'spec_helper' RSpec.describe Gitlab::ImportExport::Group::RelationFactory do let(:group) { create(:group) } let(:members_mapper) { double('members_mapper').as_null_object } - let(:user) { create(:admin) } + let(:admin) { create(:admin) } + let(:importer_user) { admin } let(:excluded_keys) { [] } let(:created_object) do - described_class.create(relation_sym: relation_sym, - relation_hash: relation_hash, - members_mapper: members_mapper, - object_builder: Gitlab::ImportExport::Group::ObjectBuilder, - user: user, - importable: group, - excluded_keys: excluded_keys) + described_class.create( + relation_sym: relation_sym, + relation_hash: relation_hash, + members_mapper: members_mapper, + object_builder: Gitlab::ImportExport::Group::ObjectBuilder, + user: importer_user, + importable: group, + excluded_keys: excluded_keys + ) end context 'label object' do @@ -24,18 +27,18 @@ RSpec.describe Gitlab::ImportExport::Group::RelationFactory do let(:relation_hash) do { - 'id' => 123456, - 'title' => 'Bruffefunc', - 'color' => '#1d2da4', - 'project_id' => nil, - 'created_at' => '2019-11-20T17:02:20.546Z', - 'updated_at' => '2019-11-20T17:02:20.546Z', - 'template' => false, + 'id' => 123456, + 'title' => 'Bruffefunc', + 'color' => '#1d2da4', + 'project_id' => nil, + 'created_at' => '2019-11-20T17:02:20.546Z', + 'updated_at' => '2019-11-20T17:02:20.546Z', + 'template' => false, 'description' => 'Description', - 'group_id' => original_group_id, - 'type' => 'GroupLabel', - 'priorities' => [], - 'textColor' => '#FFFFFF' + 'group_id' => original_group_id, + 'type' => 'GroupLabel', + 'priorities' => [], + 'textColor' => '#FFFFFF' } end @@ -60,58 +63,28 @@ RSpec.describe Gitlab::ImportExport::Group::RelationFactory do end end - context 'Notes user references' do - let(:relation_sym) { :notes } - let(:new_user) { create(:user) } - let(:exported_member) do - { - 'id' => 111, - 'access_level' => 30, - 'source_id' => 1, - 'source_type' => 'Namespace', - 'user_id' => 3, - 'notification_level' => 3, - 'created_at' => '2016-11-18T09:29:42.634Z', - 'updated_at' => '2016-11-18T09:29:42.634Z', - 'user' => { - 'id' => 999, - 'email' => new_user.email, - 'username' => new_user.username - } - } - end - + it_behaves_like 'Notes user references' do + let(:importable) { group } let(:relation_hash) do { - 'id' => 4947, - 'note' => 'note', + 'id' => 4947, + 'note' => 'note', 'noteable_type' => 'Epic', - 'author_id' => 999, - 'created_at' => '2016-11-18T09:29:42.634Z', - 'updated_at' => '2016-11-18T09:29:42.634Z', - 'project_id' => 1, - 'attachment' => { + 'author_id' => 999, + 'created_at' => '2016-11-18T09:29:42.634Z', + 'updated_at' => '2016-11-18T09:29:42.634Z', + 'project_id' => 1, + 'attachment' => { 'url' => nil }, - 'noteable_id' => 377, - 'system' => true, - 'author' => { + 'noteable_id' => 377, + 'system' => true, + 'author' => { 'name' => 'Administrator' }, 'events' => [] } end - - let(:members_mapper) do - Gitlab::ImportExport::MembersMapper.new( - exported_members: [exported_member], - user: user, - importable: group) - end - - it 'maps the right author to the imported note' do - expect(created_object.author).to eq(new_user) - end end def random_id diff --git a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb index 31cf2362628..50bc6a30044 100644 --- a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb @@ -3,19 +3,22 @@ require 'spec_helper' RSpec.describe Gitlab::ImportExport::Project::RelationFactory do - let(:group) { create(:group) } + let(:group) { create(:group) } let(:project) { create(:project, :repository, group: group) } let(:members_mapper) { double('members_mapper').as_null_object } - let(:user) { create(:admin) } + let(:admin) { create(:admin) } + let(:importer_user) { admin } let(:excluded_keys) { [] } let(:created_object) do - described_class.create(relation_sym: relation_sym, - relation_hash: relation_hash, - object_builder: Gitlab::ImportExport::Project::ObjectBuilder, - members_mapper: members_mapper, - user: user, - importable: project, - excluded_keys: excluded_keys) + described_class.create( + relation_sym: relation_sym, + relation_hash: relation_hash, + object_builder: Gitlab::ImportExport::Project::ObjectBuilder, + members_mapper: members_mapper, + user: importer_user, + importable: project, + excluded_keys: excluded_keys + ) end before do @@ -113,9 +116,9 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do "created_at" => "2016-11-18T09:29:42.634Z", "updated_at" => "2016-11-18T09:29:42.634Z", "user" => { - "id" => user.id, - "email" => user.email, - "username" => user.username + "id" => admin.id, + "email" => admin.email, + "username" => admin.username } } end @@ -123,7 +126,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do let(:members_mapper) do Gitlab::ImportExport::MembersMapper.new( exported_members: [exported_member], - user: user, + user: importer_user, importable: project) end @@ -134,9 +137,9 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do 'source_branch' => "feature_conflict", 'source_project_id' => project.id, 'target_project_id' => project.id, - 'author_id' => user.id, - 'assignee_id' => user.id, - 'updated_by_id' => user.id, + 'author_id' => admin.id, + 'assignee_id' => admin.id, + 'updated_by_id' => admin.id, 'title' => "MR1", 'created_at' => "2016-06-14T15:02:36.568Z", 'updated_at' => "2016-06-14T15:02:56.815Z", @@ -151,11 +154,11 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do end it 'has preloaded author' do - expect(created_object.author).to equal(user) + expect(created_object.author).to equal(admin) end it 'has preloaded updated_by' do - expect(created_object.updated_by).to equal(user) + expect(created_object.updated_by).to equal(admin) end it 'has preloaded source project' do @@ -264,27 +267,8 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do end end - context 'Notes user references' do - let(:relation_sym) { :notes } - let(:new_user) { create(:user) } - let(:exported_member) do - { - "id" => 111, - "access_level" => 30, - "source_id" => 1, - "source_type" => "Project", - "user_id" => 3, - "notification_level" => 3, - "created_at" => "2016-11-18T09:29:42.634Z", - "updated_at" => "2016-11-18T09:29:42.634Z", - "user" => { - "id" => 999, - "email" => new_user.email, - "username" => new_user.username - } - } - end - + it_behaves_like 'Notes user references' do + let(:importable) { project } let(:relation_hash) do { "id" => 4947, @@ -305,17 +289,6 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do "events" => [] } end - - let(:members_mapper) do - Gitlab::ImportExport::MembersMapper.new( - exported_members: [exported_member], - user: user, - importable: project) - end - - it 'maps the right author to the imported note' do - expect(created_object.author).to eq(new_user) - end end context 'encrypted attributes' do diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 22c6ef7346b..d3fb26c0a0a 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -255,6 +255,20 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ) end + it 'includes group imports usage data' do + for_defined_days_back do + user = create(:user) + group = create(:group) + group.add_owner(user) + create(:group_import_state, group: group, user: user) + end + + expect(described_class.usage_activity_by_stage_manage({})) + .to include(groups_imported: 2) + expect(described_class.usage_activity_by_stage_manage(described_class.last_28_days_time_period)) + .to include(groups_imported: 1) + end + def omniauth_providers [ OpenStruct.new(name: 'google_oauth2'), diff --git a/spec/migrations/cleanup_group_import_states_with_null_user_id_spec.rb b/spec/migrations/cleanup_group_import_states_with_null_user_id_spec.rb new file mode 100644 index 00000000000..2502c8104e3 --- /dev/null +++ b/spec/migrations/cleanup_group_import_states_with_null_user_id_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +# In order to test the CleanupGroupImportStatesWithNullUserId migration, we need +# to first create GroupImportState with NULL user_id +# and then run the migration to check that user_id was populated or record removed +# +# The problem is that the CleanupGroupImportStatesWithNullUserId migration comes +# after the NOT NULL constraint has been added with a previous migration (AddNotNullConstraintToUserOnGroupImportStates) +# That means that while testing the current class we can not insert GroupImportState records with an +# invalid user_id as constraint is blocking it from doing so +# +# To solve this problem, use SchemaVersionFinder to set schema one version prior to AddNotNullConstraintToUserOnGroupImportStates + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200907092715_add_not_null_constraint_to_user_on_group_import_states.rb') +require Rails.root.join('db', 'post_migrate', '20200909161624_cleanup_group_import_states_with_null_user_id.rb') + +RSpec.describe CleanupGroupImportStatesWithNullUserId, :migration, + schema: MigrationHelpers::SchemaVersionFinder.migration_prior(AddNotNullConstraintToUserOnGroupImportStates) do + let(:namespaces_table) { table(:namespaces) } + let(:users_table) { table(:users) } + let(:group_import_states_table) { table(:group_import_states) } + let(:members_table) { table(:members) } + + describe 'Group import states clean up' do + context 'when user_id is present' do + it 'does not update group_import_state record' do + user_1 = users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 1) + group_1 = namespaces_table.create!(name: 'group_1', path: 'group_1', type: 'Group') + create_member(user_id: user_1.id, type: 'GroupMember', source_type: 'Namespace', source_id: group_1.id, access_level: described_class::Group::OWNER) + group_import_state_1 = group_import_states_table.create!(group_id: group_1.id, user_id: user_1.id, status: 0) + + expect(group_import_state_1.user_id).to eq(user_1.id) + + disable_migrations_output { migrate! } + + expect(group_import_state_1.reload.user_id).to eq(user_1.id) + end + end + + context 'when user_id is missing' do + it 'updates user_id with group default owner id' do + user_2 = users_table.create!(name: 'user2', email: 'user2@example.com', projects_limit: 1) + group_2 = namespaces_table.create!(name: 'group_2', path: 'group_2', type: 'Group') + create_member(user_id: user_2.id, type: 'GroupMember', source_type: 'Namespace', source_id: group_2.id, access_level: described_class::Group::OWNER) + group_import_state_2 = group_import_states_table.create!(group_id: group_2.id, user_id: nil, status: 0) + + disable_migrations_output { migrate! } + + expect(group_import_state_2.reload.user_id).to eq(user_2.id) + end + end + + context 'when group does not contain any owners' do + it 'removes group_import_state record' do + group_3 = namespaces_table.create!(name: 'group_3', path: 'group_3', type: 'Group') + group_import_state_3 = group_import_states_table.create!(group_id: group_3.id, user_id: nil, status: 0) + + disable_migrations_output { migrate! } + + expect { group_import_state_3.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + + def create_member(options) + members_table.create!( + { + notification_level: 0, + ldap: false, + override: false + }.merge(options) + ) + end +end diff --git a/spec/models/group_import_state_spec.rb b/spec/models/group_import_state_spec.rb index 4404ef64966..469b5c96ac9 100644 --- a/spec/models/group_import_state_spec.rb +++ b/spec/models/group_import_state_spec.rb @@ -6,6 +6,7 @@ RSpec.describe GroupImportState do describe 'validations' do let_it_be(:group) { create(:group) } + it { is_expected.to belong_to(:user).required } it { is_expected.to validate_presence_of(:group) } it { is_expected.to validate_presence_of(:status) } diff --git a/spec/services/groups/import_export/import_service_spec.rb b/spec/services/groups/import_export/import_service_spec.rb index 4aac602a6da..f284225e23a 100644 --- a/spec/services/groups/import_export/import_service_spec.rb +++ b/spec/services/groups/import_export/import_service_spec.rb @@ -10,6 +10,15 @@ RSpec.describe Groups::ImportExport::ImportService do context 'when the job can be successfully scheduled' do subject(:import_service) { described_class.new(group: group, user: user) } + it 'creates group import state' do + import_service.async_execute + + import_state = group.import_state + + expect(import_state.user).to eq(user) + expect(import_state.group).to eq(group) + end + it 'enqueues an import job' do expect(GroupImportWorker).to receive(:perform_async).with(user.id, group.id) diff --git a/spec/services/merge_requests/export_csv_service_spec.rb b/spec/services/merge_requests/export_csv_service_spec.rb new file mode 100644 index 00000000000..8161a444231 --- /dev/null +++ b/spec/services/merge_requests/export_csv_service_spec.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe MergeRequests::ExportCsvService do + let_it_be(:merge_request) { create(:merge_request) } + let(:csv) { CSV.parse(subject.csv_data, headers: true).first } + + subject { described_class.new(MergeRequest.where(id: merge_request.id)) } + + describe 'csv_data' do + it 'contains the correct information', :aggregate_failures do + expect(csv['MR IID']).to eq(merge_request.iid.to_s) + expect(csv['Title']).to eq(merge_request.title) + expect(csv['State']).to eq(merge_request.state) + expect(csv['Description']).to eq(merge_request.description) + expect(csv['Source Branch']).to eq(merge_request.source_branch) + expect(csv['Target Branch']).to eq(merge_request.target_branch) + expect(csv['Source Project ID']).to eq(merge_request.source_project_id.to_s) + expect(csv['Target Project ID']).to eq(merge_request.target_project_id.to_s) + expect(csv['Author']).to eq(merge_request.author.name) + expect(csv['Author Username']).to eq(merge_request.author.username) + end + + describe 'assignees' do + context 'when assigned' do + let_it_be(:merge_request) { create(:merge_request, assignees: create_list(:user, 2)) } + + it 'contains the names of assignees' do + expect(csv['Assignees']).to eq(merge_request.assignees.map(&:name).join(', ')) + end + + it 'contains the usernames of assignees' do + expect(csv['Assignee Usernames']).to eq(merge_request.assignees.map(&:username).join(', ')) + end + end + + context 'when not assigned' do + it 'returns empty strings' do + expect(csv['Assignees']).to eq('') + expect(csv['Assignee Usernames']).to eq('') + end + end + end + + describe 'approvers' do + context 'when approved' do + let_it_be(:merge_request) { create(:merge_request) } + let(:approvers) { create_list(:user, 2) } + + before do + merge_request.approved_by_users = approvers + end + + it 'contains the names of approvers separated by a comma' do + expect(csv['Approvers'].split(', ')).to contain_exactly(approvers[0].name, approvers[1].name) + end + + it 'contains the usernames of approvers separated by a comma' do + expect(csv['Approver Usernames'].split(', ')).to contain_exactly(approvers[0].username, approvers[1].username) + end + end + + context 'when not approved' do + it 'returns empty strings' do + expect(csv['Approvers']).to eq('') + expect(csv['Approver Usernames']).to eq('') + end + end + end + + describe 'merged user' do + context 'MR is merged' do + let_it_be(:merge_request) { create(:merge_request, :merged, :with_merged_metrics) } + + it 'is merged' do + expect(csv['State']).to eq('merged') + end + + it 'has a merged user' do + expect(csv['Merged User']).to eq(merge_request.metrics.merged_by.name) + expect(csv['Merged Username']).to eq(merge_request.metrics.merged_by.username) + end + end + + context 'MR is not merged' do + it 'returns empty strings' do + expect(csv['Merged User']).to eq('') + expect(csv['Merged Username']).to eq('') + end + end + end + + describe 'milestone' do + context 'milestone is assigned' do + let_it_be(:merge_request) { create(:merge_request) } + let_it_be(:milestone) { create(:milestone, :active, project: merge_request.project) } + + before do + merge_request.update!(milestone_id: milestone.id) + end + + it 'contains the milestone ID' do + expect(csv['Milestone ID']).to eq(merge_request.milestone.id.to_s) + end + end + + context 'no milestone is assigned' do + it 'returns an empty string' do + expect(csv['Milestone ID']).to eq('') + end + end + end + end +end diff --git a/spec/support/migrations_helpers/schema_version_finder.rb b/spec/support/migrations_helpers/schema_version_finder.rb new file mode 100644 index 00000000000..b677db7ea26 --- /dev/null +++ b/spec/support/migrations_helpers/schema_version_finder.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Sometimes data migration specs require adding invalid test data in order to test +# the migration (e.g. adding a row with null foreign key). Certain db migrations that +# add constraints (e.g. NOT NULL constraint) prevent invalid records from being added +# and data migration from being tested. For this reason, SchemaVersionFinder can be used +# to find and use schema prior to specified one. +# +# @example +# RSpec.describe CleanupThings, :migration, schema: MigrationHelpers::SchemaVersionFinder.migration_prior(AddNotNullConstraint) do ... +# +# SchemaVersionFinder returns schema version prior to the one specified, which allows to then add +# invalid records to the database, which in return allows to properly test data migration. +module MigrationHelpers + class SchemaVersionFinder + def self.migrations_paths + ActiveRecord::Migrator.migrations_paths + end + + def self.migration_context + ActiveRecord::MigrationContext.new(migrations_paths, ActiveRecord::SchemaMigration) + end + + def self.migrations + migration_context.migrations + end + + def self.migration_prior(migration_klass) + migrations.each_cons(2) do |previous, migration| + break previous.version if migration.name == migration_klass.name + end + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/import_export/relation_factory_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/relation_factory_shared_examples.rb new file mode 100644 index 00000000000..4c14d97446e --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/import_export/relation_factory_shared_examples.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +# required context: +# - importable: group or project +# - relation_hash: a note relation that's being imported +# - created_object: the object created with the relation factory +RSpec.shared_examples 'Notes user references' do + let(:relation_sym) { :notes } + let(:mapped_user) { create(:user) } + let(:exported_member) do + { + 'id' => 111, + 'access_level' => 30, + 'source_id' => 1, + 'source_type' => importable.class.name == 'Project' ? 'Project' : 'Namespace', + 'user_id' => 3, + 'notification_level' => 3, + 'created_at' => '2016-11-18T09:29:42.634Z', + 'updated_at' => '2016-11-18T09:29:42.634Z', + 'user' => { + 'id' => 999, + 'email' => mapped_user.email, + 'username' => mapped_user.username + } + } + end + + let(:members_mapper) do + Gitlab::ImportExport::MembersMapper.new( + exported_members: [exported_member].compact, + user: importer_user, + importable: importable + ) + end + + shared_examples 'sets the note author to the importer user' do + it { expect(created_object.author).to eq(importer_user) } + end + + shared_examples 'sets the note author to the mapped user' do + it { expect(created_object.author).to eq(mapped_user) } + end + + shared_examples 'does not add original autor note' do + it { expect(created_object.note).not_to include('*By Administrator') } + end + + shared_examples 'adds original autor note' do + it { expect(created_object.note).to include('*By Administrator') } + end + + context 'when the importer is admin' do + let(:importer_user) { create(:admin) } + + context 'and the note author is not mapped' do + let(:exported_member) { nil } + + include_examples 'sets the note author to the importer user' + + include_examples 'adds original autor note' + end + + context 'and the note author is the importer user' do + let(:mapped_user) { importer_user } + + include_examples 'sets the note author to the mapped user' + + include_examples 'adds original autor note' + end + + context 'and the note author exists in the target instance' do + let(:mapped_user) { create(:user) } + + include_examples 'sets the note author to the mapped user' + + include_examples 'does not add original autor note' + end + end + + context 'when the importer is not admin' do + let(:importer_user) { create(:user) } + + context 'and the note author is not mapped' do + let(:exported_member) { nil } + + include_examples 'sets the note author to the importer user' + + include_examples 'adds original autor note' + end + + context 'and the note author is the importer user' do + let(:mapped_user) { importer_user } + + include_examples 'sets the note author to the importer user' + + include_examples 'adds original autor note' + end + + context 'and the note author exists in the target instance' do + let(:mapped_user) { create(:user) } + + include_examples 'sets the note author to the importer user' + + include_examples 'adds original autor note' + end + end +end diff --git a/spec/workers/group_import_worker_spec.rb b/spec/workers/group_import_worker_spec.rb index fb2d49c21af..3fa24ecd7bc 100644 --- a/spec/workers/group_import_worker_spec.rb +++ b/spec/workers/group_import_worker_spec.rb @@ -3,12 +3,14 @@ require 'spec_helper' RSpec.describe GroupImportWorker do - let!(:user) { create(:user) } - let!(:group) { create(:group) } + let(:user) { create(:user) } + let(:group) { create(:group) } subject { described_class.new } before do + create(:group_import_state, group: group, user: user) + allow_next_instance_of(described_class) do |job| allow(job).to receive(:jid).and_return(SecureRandom.hex(8)) end @@ -26,44 +28,11 @@ RSpec.describe GroupImportWorker do subject.perform(user.id, group.id) end - context 'when the import state does not exist' do - it 'creates group import' do - expect(group.import_state).to be_nil - - subject.perform(user.id, group.id) - import_state = group.reload.import_state - - expect(import_state).to be_instance_of(GroupImportState) - expect(import_state.status_name).to eq(:finished) - expect(import_state.jid).not_to be_empty - end - - it 'sets the group import status to started' do - expect_next_instance_of(GroupImportState) do |import| - expect(import).to receive(:start!).and_call_original - end - - subject.perform(user.id, group.id) - end - - it 'sets the group import status to finished' do - expect_next_instance_of(GroupImportState) do |import| - expect(import).to receive(:finish!).and_call_original - end + it 'updates the existing state' do + expect { subject.perform(user.id, group.id) } + .not_to change { GroupImportState.count } - subject.perform(user.id, group.id) - end - end - - context 'when the import state already exists' do - it 'updates the existing state' do - existing_state = create(:group_import_state, group: group) - - expect { subject.perform(user.id, group.id) } - .not_to change { GroupImportState.count } - - expect(existing_state.reload).to be_finished - end + expect(group.import_state.reload).to be_finished end end @@ -83,11 +52,9 @@ RSpec.describe GroupImportWorker do end it 'sets the group import status to failed' do - expect_next_instance_of(GroupImportState) do |import| - expect(import).to receive(:fail_op).and_call_original - end - expect { subject.perform(user.id, group.id) }.to raise_exception(Gitlab::ImportExport::Error) + + expect(group.import_state.reload.status).to eq(-1) end end end |