diff options
Diffstat (limited to 'spec')
83 files changed, 1167 insertions, 964 deletions
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb index 9dc4edf97d1..c59add88a82 100644 --- a/spec/bin/changelog_spec.rb +++ b/spec/bin/changelog_spec.rb @@ -95,6 +95,7 @@ describe 'bin/changelog' do it 'shows error message and exits the program' do allow($stdin).to receive(:getc).and_return(type) + expect do expect { described_class.read_type }.to raise_error( ChangelogHelpers::Abort, diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 9d10d725ff3..10e1bfc30f9 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -78,5 +78,12 @@ describe Admin::ApplicationSettingsController do expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.restricted_visibility_levels).to be_empty end + + it 'updates the receive_max_input_size setting' do + put :update, application_setting: { receive_max_input_size: "1024" } + + expect(response).to redirect_to(admin_application_settings_path) + expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) + end end end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index ca7d30fec83..751919f9501 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -166,8 +166,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to match_response_schema('job/job_details') expect(json_response['artifact']['download_path']).to match(%r{artifacts/download}) expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse}) - expect(json_response['artifact']).not_to have_key(:expired) - expect(json_response['artifact']).not_to have_key(:expired_at) + expect(json_response['artifact']).not_to have_key('expired') + expect(json_response['artifact']).not_to have_key('expired_at') end end @@ -177,8 +177,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do it 'exposes needed information' do expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') - expect(json_response['artifact']).not_to have_key(:download_path) - expect(json_response['artifact']).not_to have_key(:browse_path) + expect(json_response['artifact']).not_to have_key('download_path') + expect(json_response['artifact']).not_to have_key('browse_path') expect(json_response['artifact']['expired']).to eq(true) expect(json_response['artifact']['expire_at']).not_to be_empty end diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 1458113b90c..81badaac76b 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -154,7 +154,7 @@ describe Projects::NotesController do get :index, request_params expect(parsed_response[:notes].count).to eq(1) - expect(note_json[:id]).to eq(note.id) + expect(note_json[:id]).to eq(note.id.to_s) end it 'does not result in N+1 queries' do diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb index 17769a14def..d11e42b411b 100644 --- a/spec/controllers/projects/registry/repositories_controller_spec.rb +++ b/spec/controllers/projects/registry/repositories_controller_spec.rb @@ -86,9 +86,10 @@ describe Projects::Registry::RepositoriesController do stub_container_registry_tags(repository: :any, tags: []) end - it 'deletes a repository' do - expect { delete_repository(repository) }.to change { ContainerRepository.all.count }.by(-1) + it 'schedules a job to delete a repository' do + expect(DeleteContainerRepositoryWorker).to receive(:perform_async).with(user.id, repository.id) + delete_repository(repository) expect(response).to have_gitlab_http_status(:no_content) end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index c3a66477b6a..3bc9cbe64c5 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -803,37 +803,7 @@ describe ProjectsController do project.add_maintainer(user) end - context 'object storage disabled' do - before do - stub_feature_flags(import_export_object_storage: false) - end - - context 'when project export is enabled' do - it 'returns 302' do - get :download_export, namespace_id: project.namespace, id: project - - expect(response).to have_gitlab_http_status(302) - end - end - - context 'when project export is disabled' do - before do - stub_application_setting(project_export_enabled?: false) - end - - it 'returns 404' do - get :download_export, namespace_id: project.namespace, id: project - - expect(response).to have_gitlab_http_status(404) - end - end - end - context 'object storage enabled' do - before do - stub_feature_flags(import_export_object_storage: true) - end - context 'when project export is enabled' do it 'returns 302' do get :download_export, namespace_id: project.namespace, id: project diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index dd6525b9622..80801eb1082 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -103,27 +103,11 @@ FactoryBot.define do end trait :with_export do - before(:create) do |_project, _evaluator| - allow(Feature).to receive(:enabled?).with(:import_export_object_storage) { false } - allow(Feature).to receive(:enabled?).with('import_export_object_storage') { false } - end - after(:create) do |project, _evaluator| ProjectExportWorker.new.perform(project.creator.id, project.id) end end - trait :with_object_export do - before(:create) do |_project, _evaluator| - allow(Feature).to receive(:enabled?).with(:import_export_object_storage) { true } - allow(Feature).to receive(:enabled?).with('import_export_object_storage') { true } - end - - after(:create) do |project, evaluator| - ProjectExportWorker.new.perform(project.creator.id, project.id) - end - end - trait :broken_storage do after(:create) do |project| project.update_column(:repository_storage, 'broken') diff --git a/spec/factories/resource_label_events.rb b/spec/factories/resource_label_events.rb index a67ad78c098..739ba901052 100644 --- a/spec/factories/resource_label_events.rb +++ b/spec/factories/resource_label_events.rb @@ -2,9 +2,12 @@ FactoryBot.define do factory :resource_label_event do - user { issue.project.creator } action :add label - issue + user { issuable&.author || create(:user) } + + after(:build) do |event, evaluator| + event.issue = create(:issue) unless event.issuable + end end end diff --git a/spec/features/issues/resource_label_events_spec.rb b/spec/features/issues/resource_label_events_spec.rb new file mode 100644 index 00000000000..40c452c991a --- /dev/null +++ b/spec/features/issues/resource_label_events_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'List issue resource label events', :js do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project, author: user) } + let!(:label) { create(:label, project: project, title: 'foo') } + + context 'when user displays the issue' do + let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue, note: 'some note') } + let!(:event) { create(:resource_label_event, user: user, issue: issue, label: label) } + + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'shows both notes and resource label events' do + page.within('#notes') do + expect(find("#note_#{note.id}")).to have_content 'some note' + expect(find("#note_#{event.discussion_id}")).to have_content 'added foo label' + end + end + end + + context 'when user adds label to the issue' do + def toggle_labels(labels) + page.within '.labels' do + click_link 'Edit' + wait_for_requests + + labels.each { |label| click_link label } + + click_link 'Edit' + wait_for_requests + end + end + + before do + create(:label, project: project, title: 'bar') + project.add_developer(user) + + sign_in(user) + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'shows add note for newly added labels' do + toggle_labels(%w(foo bar)) + visit project_issue_path(project, issue) + wait_for_requests + + page.within('#notes') do + expect(page).to have_content 'added bar foo labels' + end + end + end +end diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index eb281cd2122..8a418356541 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -25,7 +25,6 @@ describe 'Import/Export - project export integration test', :js do before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) - stub_feature_flags(import_export_object_storage: false) end after do diff --git a/spec/features/projects/import_export/import_file_object_storage_spec.rb b/spec/features/projects/import_export/import_file_object_storage_spec.rb deleted file mode 100644 index 0d364543916..00000000000 --- a/spec/features/projects/import_export/import_file_object_storage_spec.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'spec_helper' - -describe 'Import/Export - project import integration test', :js do - include Select2Helper - - let(:user) { create(:user) } - let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } - let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } - - before do - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(FileUploader) - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) - gitlab_sign_in(user) - end - - after do - FileUtils.rm_rf(export_path, secure: true) - end - - context 'when selecting the namespace' do - let(:user) { create(:admin) } - let!(:namespace) { user.namespace } - let(:project_path) { 'test-project-path' + SecureRandom.hex } - - context 'prefilled the path' do - it 'user imports an exported project successfully' do - visit new_project_path - - select2(namespace.id, from: '#project_namespace_id') - fill_in :project_path, with: project_path, visible: true - click_import_project_tab - click_link 'GitLab export' - - expect(page).to have_content('Import an exported GitLab project') - expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}") - - attach_file('file', file) - click_on 'Import project' - - expect(Project.count).to eq(1) - - project = Project.last - expect(project).not_to be_nil - expect(project.description).to eq("Foo Bar") - expect(project.issues).not_to be_empty - expect(project.merge_requests).not_to be_empty - expect(project_hook_exists?(project)).to be true - expect(wiki_exists?(project)).to be true - expect(project.import_state.status).to eq('finished') - end - end - - context 'path is not prefilled' do - it 'user imports an exported project successfully' do - visit new_project_path - click_import_project_tab - click_link 'GitLab export' - - fill_in :path, with: 'test-project-path', visible: true - attach_file('file', file) - - expect { click_on 'Import project' }.to change { Project.count }.by(1) - - project = Project.last - expect(project).not_to be_nil - expect(page).to have_content("Project 'test-project-path' is being imported") - end - end - end - - it 'invalid project' do - project = create(:project, namespace: user.namespace) - - visit new_project_path - - select2(user.namespace.id, from: '#project_namespace_id') - fill_in :project_path, with: project.name, visible: true - click_import_project_tab - click_link 'GitLab export' - attach_file('file', file) - click_on 'Import project' - - page.within('.flash-container') do - expect(page).to have_content('Project could not be imported') - end - end - - def wiki_exists?(project) - wiki = ProjectWiki.new(project) - wiki.repository.exists? && !wiki.repository.empty? - end - - def project_hook_exists?(project) - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository).exists? - end - end - - def click_import_project_tab - find('#import-project-tab').click - end -end diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 2d86115de12..2936482a1f7 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -8,7 +8,7 @@ describe 'Import/Export - project import integration test', :js do let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } before do - stub_feature_flags(import_export_object_storage: false) + stub_uploads_object_storage(FileUploader) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) gitlab_sign_in(user) end @@ -33,7 +33,6 @@ describe 'Import/Export - project import integration test', :js do expect(page).to have_content('Import an exported GitLab project') expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}") - expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}\z/).and_call_original attach_file('file', file) click_on 'Import project' diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb deleted file mode 100644 index 9bb8a2063b5..00000000000 --- a/spec/features/projects/import_export/namespace_export_file_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'spec_helper' - -describe 'Import/Export - Namespace export file cleanup', :js do - let(:export_path) { Dir.mktmpdir('namespace_export_file_spec') } - - before do - allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) - stub_feature_flags(import_export_object_storage: false) - end - - after do - FileUtils.rm_rf(export_path, secure: true) - end - - shared_examples_for 'handling project exports on namespace change' do - let!(:old_export_path) { project.export_path } - - before do - sign_in(create(:admin)) - - setup_export_project - end - - context 'moving the namespace' do - it 'removes the export file' do - expect(File).to exist(old_export_path) - - project.namespace.update!(path: build(:namespace).path) - - expect(File).not_to exist(old_export_path) - end - end - - context 'deleting the namespace' do - it 'removes the export file' do - expect(File).to exist(old_export_path) - - project.namespace.destroy - - expect(File).not_to exist(old_export_path) - end - end - end - - describe 'legacy storage' do - let(:project) { create(:project, :legacy_storage) } - - it_behaves_like 'handling project exports on namespace change' - end - - describe 'hashed storage' do - let(:project) { create(:project) } - - it_behaves_like 'handling project exports on namespace change' - end - - def setup_export_project - visit edit_project_path(project) - - expect(page).to have_content('Export project') - - find(:link, 'Export project').send_keys(:return) - - visit edit_project_path(project) - - expect(page).to have_content('Download export') - end -end diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz Binary files differindex 3b5df47e0b6..730e586b278 100644 --- a/spec/features/projects/import_export/test_project_export.tar.gz +++ b/spec/features/projects/import_export/test_project_export.tar.gz diff --git a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb index 5007794cd77..18ccd31f3d0 100644 --- a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb @@ -13,7 +13,7 @@ describe 'User deletes wiki page', :js do it 'deletes a page' do click_on('Edit') click_on('Delete') - find('.js-modal-primary-action').click + find('.modal-footer .btn-danger').click expect(page).to have_content('Page was successfully deleted') end diff --git a/spec/features/usage_stats_consent_spec.rb b/spec/features/usage_stats_consent_spec.rb new file mode 100644 index 00000000000..dd8f3179895 --- /dev/null +++ b/spec/features/usage_stats_consent_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Usage stats consent' do + context 'when signed in' do + let(:user) { create(:admin, created_at: 8.days.ago) } + let(:message) { 'To help improve GitLab, we would like to periodically collect usage information.' } + + before do + allow(user).to receive(:has_current_license?).and_return false + + gitlab_sign_in(user) + end + + it 'hides the banner permanently when sets usage stats' do + visit root_dashboard_path + + expect(page).to have_content(message) + + click_link 'Send usage data' + + expect(page).not_to have_content(message) + expect(page).to have_content('Application settings saved successfully') + + gitlab_sign_out + gitlab_sign_in(user) + visit root_dashboard_path + + expect(page).not_to have_content(message) + end + + it 'shows banner on next session if user did not set usage stats' do + visit root_dashboard_path + + expect(page).to have_content(message) + + gitlab_sign_out + gitlab_sign_in(user) + visit root_dashboard_path + + expect(page).to have_content(message) + end + end +end diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb index 9155a8d6fe9..81fb4e3561c 100644 --- a/spec/finders/contributed_projects_finder_spec.rb +++ b/spec/finders/contributed_projects_finder_spec.rb @@ -8,6 +8,7 @@ describe ContributedProjectsFinder do let!(:public_project) { create(:project, :public) } let!(:private_project) { create(:project, :private) } + let!(:internal_project) { create(:project, :internal) } before do private_project.add_maintainer(source_user) @@ -16,17 +17,18 @@ describe ContributedProjectsFinder do create(:push_event, project: public_project, author: source_user) create(:push_event, project: private_project, author: source_user) + create(:push_event, project: internal_project, author: source_user) end - describe 'without a current user' do + describe 'activity without a current user' do subject { finder.execute } - it { is_expected.to eq([public_project]) } + it { is_expected.to match_array([public_project]) } end - describe 'with a current user' do + describe 'activity with a current user' do subject { finder.execute(current_user) } - it { is_expected.to eq([private_project, public_project]) } + it { is_expected.to match_array([private_project, internal_project, public_project]) } end end diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb index 58470f4c84d..c5fcd68eb4c 100644 --- a/spec/finders/user_recent_events_finder_spec.rb +++ b/spec/finders/user_recent_events_finder_spec.rb @@ -13,49 +13,25 @@ describe UserRecentEventsFinder do subject(:finder) { described_class.new(current_user, project_owner) } describe '#execute' do - context 'current user does not have access to projects' do - it 'returns public and internal events' do - records = finder.execute - - expect(records).to include(public_event, internal_event) - expect(records).not_to include(private_event) + context 'when profile is public' do + it 'returns all the events' do + expect(finder.execute).to include(private_event, internal_event, public_event) end end - context 'when current user has access to the projects' do - before do - private_project.add_developer(current_user) - internal_project.add_developer(current_user) - public_project.add_developer(current_user) - end - - context 'when profile is public' do - it 'returns all the events' do - expect(finder.execute).to include(private_event, internal_event, public_event) - end - end - - context 'when profile is private' do - it 'returns no event' do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false) - expect(finder.execute).to be_empty - end - end + context 'when profile is private' do + it 'returns no event' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false) - it 'does not include the events if the user cannot read cross project' do - expect(Ability).to receive(:allowed?).and_call_original - expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false } expect(finder.execute).to be_empty end end - context 'when current user is anonymous' do - let(:current_user) { nil } - - it 'returns public events only' do - expect(finder.execute).to eq([public_event]) - end + it 'does not include the events if the user cannot read cross project' do + expect(Ability).to receive(:allowed?).and_call_original + expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false } + expect(finder.execute).to be_empty end end end diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index 41d0dfd8939..b29a22da7c2 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -16,7 +16,7 @@ export default { expanded: true, notes: [ { - id: 1749, + id: '1749', type: 'DiffNote', attachment: null, author: { @@ -68,7 +68,7 @@ export default { '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', }, { - id: 1753, + id: '1753', type: 'DiffNote', attachment: null, author: { @@ -120,7 +120,7 @@ export default { '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', }, { - id: 1754, + id: '1754', type: 'DiffNote', attachment: null, author: { @@ -162,7 +162,7 @@ export default { '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', }, { - id: 1755, + id: '1755', type: 'DiffNote', attachment: null, author: { @@ -204,7 +204,7 @@ export default { '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', }, { - id: 1756, + id: '1756', type: 'DiffNote', attachment: null, author: { diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js index 41d8bfff7e7..b45ae5bbb0f 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js @@ -30,7 +30,7 @@ describe('Multi-file editor commit sidebar list item', () => { }); it('renders file path', () => { - expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path); + expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent).toContain(f.path); }); it('renders actionn button', () => { diff --git a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js index a5b906da8a1..e09ccbe2a63 100644 --- a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js @@ -29,7 +29,7 @@ describe('IDE stage file button', () => { }); it('renders button to discard & stage', () => { - expect(vm.$el.querySelectorAll('.btn').length).toBe(2); + expect(vm.$el.querySelectorAll('.btn-blank').length).toBe(2); }); it('calls store with stage button', () => { @@ -39,7 +39,7 @@ describe('IDE stage file button', () => { }); it('calls store with discard button', () => { - vm.$el.querySelector('.dropdown-menu button').click(); + vm.$el.querySelector('.btn-danger').click(); expect(vm.discardFileChanges).toHaveBeenCalledWith(f.path); }); diff --git a/spec/javascripts/ide/components/repo_commit_section_spec.js b/spec/javascripts/ide/components/repo_commit_section_spec.js index 30cd92b2ca4..d09ccd7ac34 100644 --- a/spec/javascripts/ide/components/repo_commit_section_spec.js +++ b/spec/javascripts/ide/components/repo_commit_section_spec.js @@ -111,7 +111,7 @@ describe('RepoCommitSection', () => { .then(vm.$nextTick) .then(() => { expect(vm.$el.querySelector('.ide-commit-list-container').textContent).toContain( - 'No changes', + 'There are no unstaged changes', ); }) .then(done) @@ -133,7 +133,7 @@ describe('RepoCommitSection', () => { }); it('discards a single file', done => { - vm.$el.querySelector('.multi-file-discard-btn .dropdown-menu button').click(); + vm.$el.querySelector('.multi-file-commit-list li:first-child .js-modal-primary-action').click(); Vue.nextTick(() => { expect(vm.$el.querySelector('.ide-commit-list-container').textContent).not.toContain('file1'); diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index bca2033ff97..1ca811e996b 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -692,21 +692,6 @@ describe('IDE store file actions', () => { .then(done) .catch(done.fail); }); - - it('calls scrollToTab', done => { - const scrollToTabSpy = jasmine.createSpy('scrollToTab'); - const oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line - store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line - - store - .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }) - .then(() => { - expect(scrollToTabSpy).toHaveBeenCalled(); - store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line - }) - .then(done) - .catch(done.fail); - }); }); describe('removePendingTab', () => { diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index d84f1717a61..c9a1158a14e 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -305,7 +305,11 @@ describe('Multi-file store actions', () => { describe('stageAllChanges', () => { it('adds all files from changedFiles to stagedFiles', done => { - store.state.changedFiles.push(file(), file('new')); + const openFile = { ...file(), path: 'test' }; + + store.state.openFiles.push(openFile); + store.state.stagedFiles.push(openFile); + store.state.changedFiles.push(openFile, file('new')); testAction( stageAllChanges, @@ -316,7 +320,12 @@ describe('Multi-file store actions', () => { { type: types.STAGE_CHANGE, payload: store.state.changedFiles[0].path }, { type: types.STAGE_CHANGE, payload: store.state.changedFiles[1].path }, ], - [], + [ + { + type: 'openPendingTab', + payload: { file: openFile, keyPrefix: 'staged' }, + }, + ], done, ); }); @@ -324,7 +333,11 @@ describe('Multi-file store actions', () => { describe('unstageAllChanges', () => { it('removes all files from stagedFiles after unstaging', done => { - store.state.stagedFiles.push(file(), file('new')); + const openFile = { ...file(), path: 'test' }; + + store.state.openFiles.push(openFile); + store.state.changedFiles.push(openFile); + store.state.stagedFiles.push(openFile, file('new')); testAction( unstageAllChanges, @@ -334,7 +347,12 @@ describe('Multi-file store actions', () => { { type: types.UNSTAGE_CHANGE, payload: store.state.stagedFiles[0].path }, { type: types.UNSTAGE_CHANGE, payload: store.state.stagedFiles[1].path }, ], - [], + [ + { + type: 'openPendingTab', + payload: { file: openFile, keyPrefix: 'unstaged' }, + }, + ], done, ); }); diff --git a/spec/javascripts/lib/utils/mock_data.js b/spec/javascripts/lib/utils/mock_data.js index fd0d62b751f..93d0d6259b9 100644 --- a/spec/javascripts/lib/utils/mock_data.js +++ b/spec/javascripts/lib/utils/mock_data.js @@ -2,4 +2,4 @@ export const faviconDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACA export const overlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA85JREFUWAntVllIVGEUPv/9b46O41KplYN7PeRkti8TjQlhCUGh3MmeQugpIsGKAi2soIcIooiohxYKK2daqDAlIpIiWwxtQaJcaHE0d5tMrbn37z9XRqfR0TvVW56Hudf//uec72zfEWBCJjIwkYGJDPzvGSD/KgExN3Oi2Q+2DJgSDYQEMwItVGH1iZGmJw/Si1y+/PwVAMYYib22MYc/8hVQFgKDEfYoId0KYzagAQebsos/ewMZoeB9wdffcTYpQSaCTWHKoqSQaDk7zkIt0+aCUR8BelEHrf3dUNv9AcqbnsHtT5UKB/hTASh0SLYjnjb/CIDRJi0XiFAaJOpCD8zLpdb4NB66b1OfelthX815dtdRRfiti2aAXLvVLiMQ6olGyztGDkSo4JGGXk8/QFdGpYzpHG2GBQTDhtgVhPEaVbbVpvI6GJz22rv4TcAfrYI1x7Rj5MWWAppomKFVVb2302SFzUkZHAbkG+0b1+Gh77yNYjrmqnWTrLBLRxdvBWv8qlFujH/kYjJYyvLkj71t78zAUvzMAMnHhpN4zf9UREJhd8omyssxu1IgazQDwDnHUcNuH6vhPIE1fmuBzHt74Hn7W89jWGtcAjoaIDOFrdcMYJBkgOCoaRF0Lj0oglddDbCj6tRvKjphEpgjkzEQs2YAKsNxMzjn3nKurhzK+Ly7xe28ua8TwgMMcHJZnvvT0BPtEEKM4tDJ+C8GvIIk4ylINIXVZ0EUKJxYuh3mhCeokbudl6TtVc88dfBdLwbyaWB6zQCYQJpBYSrDGQxBQ/ZWRM2B+VNmQnVnHWx7elyNuL2/R336co7KyJR8CL9oLgEuFlREevWUkEl6uGwpVEG4FBm0OEf9N10NMgPlvWYAuNVwsWDKvcUNYsHUWTCZ13ysyFEXe6TO6aC8CUr9IiK+A05TQrc8yjwmxARHeeMAPlfQJw+AQRwu0YhL/GDXi9NwufG+S8dYkuYMqIb4SsWthotlNMOUCOM6r+G9cqXxPmd1dqrBav/o1zJy2l5/NUjJA/VORwYuFnOUaTQcPs9wMqwV++Xv8oADxKAcZ8nLPr8AoGW+xR6HSqYk3GodAz2QNj0V+Gr26dT9ASNH5239Pf0gktVNWZca8ZvfAFBprWS6hSu1pqt++Y0PD+WIwDAhIWQGtzvSHDbcodfFUFB9hg1Gjs5LXqIdFL+acFBl+FddqYwdxsWC3I70OvgfUaA65zhq2O2c8VxYcyIGFTVlXegYtvCXANCQZJMobjVcLMjtSK/IcEgyOOe8Ve5w7ryKDefp2P3+C/5ohv8HZmVLAAAAAElFTkSuQmCC'; -export const faviconWithOverlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGt0lEQVRYR8WWf3DT5R3H35/vN0lDiCztSuiPAEnTFhSOUamIzFGokwMH55g0E845yjbP6+4qIoiHY6JjnHLeRI6h9jgQpQcD3ImH2u00eHBwjGthKLI1TSm26S8oKYVS0vT7/X52z7ckTUhqC/yx55/kks/zeb+ez6/nIWYm/B8XJQB4SGq6lL+CJA47vvRtvWs2D0nNl/Kf0qBZxx6u23arv0QAAIHivK8BynB4ffa7BgDQXJx/ngGnw+uThgTwP5ZnMocoJAxVxZDVZ+0L5n5WF75TkMafjLdJxpSg2E+gqW1X7zk3rbpaifhLiEBgTv4mEFbpBoTyu01DoDj/dQAv9rvjtdnp/k3Yx9rgAMV5QYCsAAwAgg6vL/1OTy/2BYrzzwLIBWACuNHhrXPG+otGoKaw0JA58kqGJtOFfgPS8yWT4sz88nzj7UIIfz+wd0mRdEZPLMnp2V/8R0+JrhLbBYFHJvwWzBUxYgqYNzhG+zfEhm24MIE5ectBtP0W+y0Or29FcoDifHFSRxwAcMrh9c0YrmisXaA4r0V0U8xvopgDDq9PpCQ+Ag0/zbEbNUNbMiG9fTwkDTsKHpJa2t1Zmiw1AqLg+tMZ+R6WVVtnZ2qP6Ib+FIjh05G3lsDrB4xjUIbRDeM+WZLJYZ4B1rKMzKPm/fdyzs9qg6WT225IMnPcuYjxbPZhn57qaA00zc4/QYT7b1b/wAZmDYSLjsN1WcmiM+6jXz7JTCs1aNPASBjrtrCGOXVBLK9ph72772bc0REZcsQlkEVoOxblhaFBH0Bxi6GBWFNC8gpV0XqYSe/hI85R9o1zxr/QaZbdbmuW9oRzljRrzBRkW9JhMaTgYugKzl35DlXNJ/Fp43FImoZnz7T0ln7bLihM0g85N627vkWPgLrbvYyCvAP1+rRIWETA5QsyQlcJYOCbMRasWpALtljwSsFyeJxFYsoNWqdN1y/ildM78Y/WGjxx8TL+ol3oluy8VupKe7cfoNLdCJkdqEUPOmBJ5ksJoae91mBps5lQ6pkIm20MPiz6A3KsmcNukDe/3Ye3zh3A77Q2XqcGjslLz88i/nB8pkpSoL8nAFSTBpUN4qSxS5KB5jOGUOniCebmzFQcevSN2xKP+Fp7ajt21f8TOxU/5i45JZFS6XwcTB9HxZgUnGTRNgk31x5jet+aGU7jWw+UweOcPeyTxxoqrGL25+UwdjehSvnmOVIqcz4C8y8GAABcQwjnYI5NheikhQWT+EZmDh2ev/l7cz4U2cGmYyg78TYqVH87Kbtd1wFY4hsVQAt14zu2RiDaTUZMf/BHWD35STx37wDv94k1dLeh7MRmvDZ1GR5Inxg17dX6MPnjZfh5X6tGSqXrV2B8ACIx98UNGOlV4CxCuA6zqIeq9FQ8c68bhx7ZiIK06CQdVF+Il3y1Hq03gnDfk4Uj8zbH2T51dCPOtlW39Q+iPTl2VSMfwKPiKw8aTuhgpl1Zdqxzj8PphRWwm21xZjv9VcgYkYb52dP132PFbSYr/la0DpNtrrg9a2oqsKfB2zlwG+4nSe1z7QDjaQBi2Eh6J4QRwimYt43LwOsuB2oX7YLVMCLqTAya3xx/EwZJxtYHy3WhyMkHExebXz3zAbbXfdo7AFBRaMAz1Ypa6XoaoPejKRGteZm6D3SlWVdOcOHo/Lfj2u9aXw+WHNmA00G/DiFEO0Jd+meyk0fIf/+vLfik6Xhj4qN0v7i5HCY1bBQPk+ij9GSzNbzYNdH03kMrscARfzvHQgiBocSFTVHVCrW+u+WrpK9iCIgS1rRK93oG/1GkRJVIup8KMNs1Sw/1rUtALD36ZzRca8XeJDmPtRc18vDn5SCJViYHENY3IZTK3JkE7RAYtpdkp3bAaJeOzN+CsSMTX+wqa7ih9sbVSLI2WV3znihAJYXZPThA7M6KQoM2MniyhUxTioxTpKLMadjx8Jqh5k3S//8d9GOh92XWmP/aXLKvfHgA0ZTklL0jj9m6UR6L5+9bjFWTPLcFIWbCY1+8pHb0drWybJ4aWLQrODyAWJndzoyylNyGg0hL+bV7Ll4rKIWB5CFBxMlLj21SL4W6QjDQjwOL9n4tNt0+AADPfo+UqgXPHJLSJrkso7F6ylLMy56OFMmYACIKblvtQext8Iqp0swyLYiI3zEAbs6Ml3cXv/p3Y+ryq5KcnSKb1Jmj75P7X0Rm/UV0tvO86r/WIhORwszvkmHEehH2WMo7ikDUQUWhoaIG+NNc96Os8eMEmklE2Qy2ANTO0OrA+CwFOFBfsq8pWZ7+B25aDBxvPp+QAAAAAElFTkSuQmCC'; +export const faviconWithOverlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGtElEQVRYR8WXf3CT9R3H35/nSdIQIktrCf0RStI0FYRjVBAccxTq5MDBKUoz4ZyjbPO87q4yBsPDMdExTjlvIsdQexyI0oMBeuKhdjsNHhwcMgpjIlublLIm/UlJKZSSJs/z/e6+T5v0CQ22wB/7/pPck8/383l9fj6fEOec8H88NAjAS1LwknsFSVLU8WXd1rtm85LUeKnwGQKzjj3s33azvsEAAEIlnn8ByHL4/Pa7BgAQLCm8QOBOh88vDQkQeMxjMkcQEYKqYsyJWWPhgs/80TsFafzROJtkNIXFfYI0pfXqPeennjqlxPUNikBoTuEmEF+lCRBV3G0aQiWFrwH8d30AWJubGdiEfZzdGqDEEwbICnADQGGHry7zTr0X94IlnnMACggwAWh0+PxOvb5EBGqmTTNkj7ySxWS62C+g5Usm1Zn95YXG24UQ+r5n75Li6Ux4LBkyc7/4t5YSLSr6Lgg9UvBLcKocMEYKON/gGB3YoA/bcGFCczzLQdieLE9bHL66FakBSjzCU0cSAHDa4at7aLhG9XLBEk8zAVnxZxyIEhBy+PwFgwAafpxvNzK5NZUhrX28JA07Cl6SmtvcOUwm4ZAouHj7ad+jMrN1dqb3iG7oS4EYPh2etQS+XiesC8TQ3ZD3yZJsHuUPgbMcI+ej5v3ncv5PasNlk1p7JJnzJL+I0/O5h+u0VCdqIDi78AQRHuirft3hYJzQPvawPydVdPI+/OnTnNNKBjYVXHRa8rFFGeb4w1he0wZ7d/84IXTEhxzxUsgitB2LPFGwvgGUfLSeZUpEXqEqrIdz0nr4iHOUfeOccb/tNMtutzWHPeWcJc0aMxm5lkxYDGloj1zB+Sv/RXXTSXzaeBwSY3j+bHNv2bdtMYCbpHtRkNFd36xFQN3tXkZhvgP1fdPi5kMEXL4oIXKVAA58M8aCVQs84BYLXi5aDq+zGJTqYr+i4PV2vHxmJ/7WUoOn2i/jz6yhW7JjrdSV8U4fQFV+I2Q4UIsedMCSSlcsgp72WtnSajOhzDsBNtsYfFD8e+Rbs4fdIG98uw9vnj+AX7FWvk4NHZOXXphF/INx2SpJIU2L8L4GDAoMwlP9kWSg6awcKVs83tyUnY5Dj75+W8bjutae3o5d9X/HTiWAuUtOS6RUOR8Hp48TxjgU/AMSeKJ1Ej/tMWXG1sxwGt98sBxe5+xhe64XVLiK2Z9XwNgdRLXyzQsC4ENwelIHAFxDBOdh1qdCdNLCoon8RnY+HZ6/+TtzPhTZweAxlJ94C5VqoI2U3a7rACzJjQqgBd24CGscos1kxPQZ38fqSU/jhQkDvN9lrKG7FeUnNuPVKcvwYOb4hGgvi2HSx8vwRKyJkVLl+hk43gdBAcfADBD1cA4RXIdZ1EN1Zjqem+DGoUc2oigjMUlvaV8YL/1qPVpuhOG+JwdH5m1Okn3m6Eacaz3V2jeI9uTbVYY6AKOSKw8MX0MBg2lXjh3r3Hk4s7ASdrMtSWxnoBpZIzIwP3e69lxv3Gay4q/F6zDJ5kq6s6amEnsafJ0Db8P9JKkx1w5wPJuY36IToojgNMzb8rLwmsuB2kW7YDWMSCgTg+YXx9+AQZKxdUaFZiju+a2Mi8uvnH0f2/2f9g4AVE4z4LlTilrlehag9xIpEam4jO4DXfdaV97nwtH5byW137VYD5Yc2YAz4YAGIYx2RLq0z1Sex8l//fUWfBI83jh4Kd1PEuAwqVGjWEwSS+nJJmt0sWu86d0frMQCR/LbWQ8hDAxlXMgUV69Q67ubv0q5FUNAlHKmVLnXE/gfREpUiaQHqAizXbO0UN98BMTSo39Cw7UW7E2Rc728qJGHP68ASbQyNYCQTkAUzCSwQ+CwvSjnsQPGLOnI/C0YO3Lwxq5yhhtqb1KNpGqT1TXvigJU0jh33xpAf7NymoGNDJ9sJtPkYuNkqTh7KnY8vGaoeZPy93+GA1joe4kzzv/SVLqvYngA/dFgVfnlb8tjtm6Ux+I39y/Gqone24IQM+GxL15UO3q7WrhsnhJatCs8PAC9md3OrPK0goaDyEj7uXsuXi0qg4HkIUGE52XHNqmXIl0RGOiHoUV7xb+v5K14SC39At79Ximdhc8ekjImuiyjsXryUszLnY40yThIhSi4bbUHsbfBJ6ZKE5dpQdz4HQOgf2a8tLvklY+M6cuvSnJummxSZ46+X+7biMzaRnSu84IauNYsE5HCOX+HDCPWi7DrKW8/BTcVZ2UN8Me57kc5448TaCYR5XJwC0BtHMwPjs/SgAP1pfuCqSL8Pxhr/wunLWAOAAAAAElFTkSuQmCC'; diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js index 52cc42cb53d..d7298cb3483 100644 --- a/spec/javascripts/notes/components/note_actions_spec.js +++ b/spec/javascripts/notes/components/note_actions_spec.js @@ -28,7 +28,7 @@ describe('issue_note_actions component', () => { canEdit: true, canAwardEmoji: true, canReportAsAbuse: true, - noteId: 539, + noteId: '539', noteUrl: 'https://localhost:3000/group/project/merge_requests/1#note_1', reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26', @@ -59,6 +59,20 @@ describe('issue_note_actions component', () => { expect(vm.$el.querySelector(`a[href="${props.reportAbusePath}"]`)).toBeDefined(); }); + it('should be possible to copy link to a note', () => { + expect(vm.$el.querySelector('.js-btn-copy-note-link')).not.toBeNull(); + }); + + it('should not show copy link action when `noteUrl` prop is empty', done => { + vm.noteUrl = ''; + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-btn-copy-note-link')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + it('should be possible to delete comment', () => { expect(vm.$el.querySelector('.js-note-delete')).toBeDefined(); }); @@ -77,7 +91,7 @@ describe('issue_note_actions component', () => { canEdit: false, canAwardEmoji: false, canReportAsAbuse: false, - noteId: 539, + noteId: '539', noteUrl: 'https://localhost:3000/group/project/merge_requests/1#note_1', reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26', diff --git a/spec/javascripts/notes/components/note_awards_list_spec.js b/spec/javascripts/notes/components/note_awards_list_spec.js index 9d98ba219da..6a6a810acff 100644 --- a/spec/javascripts/notes/components/note_awards_list_spec.js +++ b/spec/javascripts/notes/components/note_awards_list_spec.js @@ -30,7 +30,7 @@ describe('note_awards_list component', () => { propsData: { awards: awardsMock, noteAuthorId: 2, - noteId: 545, + noteId: '545', canAwardEmoji: true, toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji', }, @@ -70,7 +70,7 @@ describe('note_awards_list component', () => { propsData: { awards: awardsMock, noteAuthorId: 2, - noteId: 545, + noteId: '545', canAwardEmoji: false, toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji', }, diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js index 95d400ab3df..147ffcf1b81 100644 --- a/spec/javascripts/notes/components/note_form_spec.js +++ b/spec/javascripts/notes/components/note_form_spec.js @@ -19,7 +19,7 @@ describe('issue_note_form component', () => { props = { isEditing: false, noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.', - noteId: 545, + noteId: '545', }; vm = new Component({ @@ -32,6 +32,22 @@ describe('issue_note_form component', () => { vm.$destroy(); }); + describe('noteHash', () => { + it('returns note hash string based on `noteId`', () => { + expect(vm.noteHash).toBe(`#note_${props.noteId}`); + }); + + it('return note hash as `#` when `noteId` is empty', done => { + vm.noteId = ''; + Vue.nextTick() + .then(() => { + expect(vm.noteHash).toBe('#'); + }) + .then(done) + .catch(done.fail); + }); + }); + describe('conflicts editing', () => { it('should show conflict message if note changes outside the component', done => { vm.isEditing = true; diff --git a/spec/javascripts/notes/components/note_header_spec.js b/spec/javascripts/notes/components/note_header_spec.js index a3c6bf78988..379780f43a0 100644 --- a/spec/javascripts/notes/components/note_header_spec.js +++ b/spec/javascripts/notes/components/note_header_spec.js @@ -33,7 +33,7 @@ describe('note_header component', () => { }, createdAt: '2017-08-02T10:51:58.559Z', includeToggle: false, - noteId: 1394, + noteId: '1394', expanded: true, }, }).$mount(); @@ -47,6 +47,16 @@ describe('note_header component', () => { it('should render timestamp link', () => { expect(vm.$el.querySelector('a[href="#note_1394"]')).toBeDefined(); }); + + it('should not render user information when prop `author` is empty object', done => { + vm.author = {}; + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.note-header-author-name')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); }); describe('discussion', () => { @@ -66,7 +76,7 @@ describe('note_header component', () => { }, createdAt: '2017-08-02T10:51:58.559Z', includeToggle: true, - noteId: 1395, + noteId: '1395', expanded: true, }, }).$mount(); diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 0423fcb6ec4..1f030e5af28 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -66,7 +66,7 @@ export const individualNote = { individual_note: true, notes: [ { - id: 1390, + id: '1390', attachment: { url: null, filename: null, @@ -111,7 +111,7 @@ export const individualNote = { }; export const note = { - id: 546, + id: '546', attachment: { url: null, filename: null, @@ -174,7 +174,7 @@ export const discussionMock = { expanded: true, notes: [ { - id: 1395, + id: '1395', attachment: { url: null, filename: null, @@ -211,7 +211,7 @@ export const discussionMock = { path: '/gitlab-org/gitlab-ce/notes/1395', }, { - id: 1396, + id: '1396', attachment: { url: null, filename: null, @@ -257,7 +257,7 @@ export const discussionMock = { path: '/gitlab-org/gitlab-ce/notes/1396', }, { - id: 1437, + id: '1437', attachment: { url: null, filename: null, @@ -308,7 +308,7 @@ export const discussionMock = { }; export const loggedOutnoteableData = { - id: 98, + id: '98', iid: 26, author_id: 1, description: '', @@ -358,7 +358,7 @@ export const collapseNotesMock = [ individual_note: true, notes: [ { - id: 1390, + id: '1390', attachment: null, author: { id: 1, @@ -393,7 +393,7 @@ export const collapseNotesMock = [ individual_note: true, notes: [ { - id: 1391, + id: '1391', attachment: null, author: { id: 1, @@ -433,7 +433,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = { expanded: true, notes: [ { - id: 1390, + id: '1390', attachment: { url: null, filename: null, @@ -495,7 +495,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = { expanded: true, notes: [ { - id: 1391, + id: '1391', attachment: { url: null, filename: null, @@ -544,7 +544,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = { '/gitlab-org/gitlab-ce/notes/1471': { commands_changes: null, valid: true, - id: 1471, + id: '1471', attachment: null, author: { id: 1, @@ -600,7 +600,7 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = { expanded: true, notes: [ { - id: 1471, + id: '1471', attachment: { url: null, filename: null, @@ -671,7 +671,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 901, + id: '901', type: null, attachment: null, author: { @@ -718,7 +718,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 902, + id: '902', type: null, attachment: null, author: { @@ -765,7 +765,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 903, + id: '903', type: null, attachment: null, author: { @@ -809,7 +809,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 904, + id: '904', type: null, attachment: null, author: { @@ -854,7 +854,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 905, + id: '905', type: null, attachment: null, author: { @@ -898,7 +898,7 @@ export const notesWithDescriptionChanges = [ expanded: true, notes: [ { - id: 906, + id: '906', type: null, attachment: null, author: { @@ -945,7 +945,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 901, + id: '901', type: null, attachment: null, author: { @@ -992,7 +992,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 902, + id: '902', type: null, attachment: null, author: { @@ -1039,7 +1039,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 904, + id: '904', type: null, attachment: null, author: { @@ -1084,7 +1084,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 905, + id: '905', type: null, attachment: null, author: { @@ -1129,7 +1129,7 @@ export const collapsedSystemNotes = [ expanded: true, notes: [ { - id: 906, + id: '906', type: null, attachment: null, author: { diff --git a/spec/javascripts/vue_shared/components/notes/system_note_spec.js b/spec/javascripts/vue_shared/components/notes/system_note_spec.js index 2a6015fe35f..adcb1c858aa 100644 --- a/spec/javascripts/vue_shared/components/notes/system_note_spec.js +++ b/spec/javascripts/vue_shared/components/notes/system_note_spec.js @@ -9,7 +9,7 @@ describe('system note component', () => { beforeEach(() => { props = { note: { - id: 1424, + id: '1424', author: { id: 1, name: 'Root', diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index c73c6023b60..0a7682d906b 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -189,9 +189,9 @@ describe API::Helpers::Pagination do it 'it returns the right link to the next page' do allow(subject).to receive(:params) .and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 }) + expect_header('X-Per-Page', '2') expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[6].id}&ks_prev_name=#{projects[6].name}&pagination=keyset&per_page=2") - expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') end diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb index 4463c011522..1ad7f3ff567 100644 --- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb +++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb @@ -3,49 +3,73 @@ require 'spec_helper' describe Banzai::Filter::SpacedLinkFilter do include FilterSpecHelper - let(:link) { '[example](page slug)' } + let(:link) { '[example](page slug)' } + let(:image) { '' } - it 'converts slug with spaces to a link' do - doc = filter("See #{link}") + context 'when a link is detected' do + it 'converts slug with spaces to a link' do + doc = filter("See #{link}") - expect(doc.at_css('a').text).to eq 'example' - expect(doc.at_css('a')['href']).to eq 'page%20slug' - expect(doc.at_css('p')).to eq nil - end + expect(doc.at_css('a').text).to eq 'example' + expect(doc.at_css('a')['href']).to eq 'page%20slug' + expect(doc.at_css('a')['title']).to be_nil + expect(doc.at_css('p')).to be_nil + end - it 'converts slug with spaces and a title to a link' do - link = '[example](page slug "title")' - doc = filter("See #{link}") + it 'converts slug with spaces and a title to a link' do + link = '[example](page slug "title")' + doc = filter("See #{link}") - expect(doc.at_css('a').text).to eq 'example' - expect(doc.at_css('a')['href']).to eq 'page%20slug' - expect(doc.at_css('a')['title']).to eq 'title' - expect(doc.at_css('p')).to eq nil - end + expect(doc.at_css('a').text).to eq 'example' + expect(doc.at_css('a')['href']).to eq 'page%20slug' + expect(doc.at_css('a')['title']).to eq 'title' + expect(doc.at_css('p')).to be_nil + end - it 'does nothing when markdown_engine is redcarpet' do - exp = act = link - expect(filter(act, markdown_engine: :redcarpet).to_html).to eq exp - end + it 'does nothing when markdown_engine is redcarpet' do + exp = act = link + expect(filter(act, markdown_engine: :redcarpet).to_html).to eq exp + end + + it 'does nothing with empty text' do + link = '[](page slug)' + doc = filter("See #{link}") + + expect(doc.at_css('a')).to be_nil + end - it 'does nothing with empty text' do - link = '[](page slug)' - doc = filter("See #{link}") + it 'does nothing with an empty slug' do + link = '[example]()' + doc = filter("See #{link}") - expect(doc.at_css('a')).to eq nil + expect(doc.at_css('a')).to be_nil + end end - it 'does nothing with an empty slug' do - link = '[example]()' - doc = filter("See #{link}") + context 'when an image is detected' do + it 'converts slug with spaces to an iamge' do + doc = filter("See #{image}") + + expect(doc.at_css('img')['src']).to eq 'img%20test.jpg' + expect(doc.at_css('img')['alt']).to eq 'example' + expect(doc.at_css('p')).to be_nil + end + + it 'converts slug with spaces and a title to an image' do + image = '' + doc = filter("See #{image}") - expect(doc.at_css('a')).to eq nil + expect(doc.at_css('img')['src']).to eq 'img%20test.jpg' + expect(doc.at_css('img')['alt']).to eq 'example' + expect(doc.at_css('img')['title']).to eq 'title' + expect(doc.at_css('p')).to be_nil + end end it 'converts multiple URLs' do link1 = '[first](slug one)' link2 = '[second](http://example.com/slug two)' - doc = filter("See #{link1} and #{link2}") + doc = filter("See #{link1} and #{image} and #{link2}") found_links = doc.css('a') @@ -54,6 +78,12 @@ describe Banzai::Filter::SpacedLinkFilter do expect(found_links[0]['href']).to eq 'slug%20one' expect(found_links[1].text).to eq 'second' expect(found_links[1]['href']).to eq 'http://example.com/slug%20two' + + found_images = doc.css('img') + + expect(found_images.size).to eq(1) + expect(found_images[0]['src']).to eq 'img%20test.jpg' + expect(found_images[0]['alt']).to eq 'example' end described_class::IGNORE_PARENTS.each do |elem| diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 52b8c9be647..64ca3ec345d 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -178,4 +178,25 @@ describe Banzai::Pipeline::WikiPipeline do end end end + + describe 'videos' do + let(:namespace) { create(:namespace, name: "wiki_link_ns") } + let(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) } + let(:project_wiki) { ProjectWiki.new(project, double(:user)) } + let(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) } + + it 'generates video html structure' do + markdown = "" + output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) + + expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video_file_name.mp4"') + end + + it 'rewrites and replaces video links names with white spaces to %20' do + markdown = "" + output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) + + expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video%20file%20name.mp4"') + end + end end diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb index cf40c467c72..494c0561975 100644 --- a/spec/lib/forever_spec.rb +++ b/spec/lib/forever_spec.rb @@ -7,6 +7,7 @@ describe Forever do context 'when using PostgreSQL' do it 'should return Postgresql future date' do allow(Gitlab::Database).to receive(:postgresql?).and_return(true) + expect(subject).to eq(described_class::POSTGRESQL_DATE) end end @@ -14,6 +15,7 @@ describe Forever do context 'when using MySQL' do it 'should return MySQL future date' do allow(Gitlab::Database).to receive(:postgresql?).and_return(false) + expect(subject).to eq(described_class::MYSQL_DATE) end end diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb index 11e605eece6..bf130b8fabd 100644 --- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb +++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb @@ -132,7 +132,6 @@ describe Gitlab::Cleanup::ProjectUploads do let!(:path) { File.join(FileUploader.root, orphaned.model.full_path, orphaned.path) } before do - stub_feature_flags(import_export_object_storage: true) stub_uploads_object_storage(FileUploader) FileUtils.mkdir_p(File.dirname(path)) @@ -156,7 +155,6 @@ describe Gitlab::Cleanup::ProjectUploads do let!(:new_path) { File.join(FileUploader.root, '-', 'project-lost-found', 'wrong', orphaned.path) } before do - stub_feature_flags(import_export_object_storage: true) stub_uploads_object_storage(FileUploader) FileUtils.mkdir_p(File.dirname(path)) diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb index 2c63f3b0455..6d29044ffd5 100644 --- a/spec/lib/gitlab/contributions_calendar_spec.rb +++ b/spec/lib/gitlab/contributions_calendar_spec.rb @@ -62,13 +62,16 @@ describe Gitlab::ContributionsCalendar do expect(calendar.activity_dates).to eq(last_week => 2, today => 1) end - it "only shows private events to authorized users" do - create_event(private_project, today) - create_event(feature_project, today) + context "when the user has opted-in for private contributions" do + it "shows private and public events to all users" do + user.update_column(:include_private_contributions, true) + create_event(private_project, today) + create_event(public_project, today) - expect(calendar.activity_dates[today]).to eq(0) - expect(calendar(user).activity_dates[today]).to eq(0) - expect(calendar(contributor).activity_dates[today]).to eq(2) + expect(calendar.activity_dates[today]).to eq(1) + expect(calendar(user).activity_dates[today]).to eq(1) + expect(calendar(contributor).activity_dates[today]).to eq(2) + end end it "counts the diff notes on merge request" do @@ -128,7 +131,7 @@ describe Gitlab::ContributionsCalendar do e3 = create_event(feature_project, today) create_event(public_project, last_week) - expect(calendar.events_by_date(today)).to contain_exactly(e1) + expect(calendar.events_by_date(today)).to contain_exactly(e1, e3) expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3) end diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb new file mode 100644 index 00000000000..bfcfed4231f --- /dev/null +++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Diff::HighlightCache do + let(:merge_request) { create(:merge_request_with_diffs) } + + subject(:cache) { described_class.new(merge_request.diffs, backend: backend) } + + describe '#decorate' do + let(:backend) { double('backend').as_null_object } + + # Manually creates a Diff::File object to avoid triggering the cache on + # the FileCollection::MergeRequestDiff + let(:diff_file) do + diffs = merge_request.diffs + raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['CHANGELOG'])).first + Gitlab::Diff::File.new(raw_diff, + repository: diffs.project.repository, + diff_refs: diffs.diff_refs, + fallback_diff_refs: diffs.fallback_diff_refs) + end + + it 'does not calculate highlighting when reading from cache' do + cache.write_if_empty + cache.decorate(diff_file) + + expect_any_instance_of(Gitlab::Diff::Highlight).not_to receive(:highlight) + + diff_file.highlighted_diff_lines + end + + it 'assigns highlighted diff lines to the DiffFile' do + cache.write_if_empty + cache.decorate(diff_file) + + expect(diff_file.highlighted_diff_lines.size).to be > 5 + end + + it 'submits a single reading from the cache' do + cache.decorate(diff_file) + cache.decorate(diff_file) + + expect(backend).to have_received(:read).with(cache.key).once + end + end + + describe '#write_if_empty' do + let(:backend) { double('backend', read: {}).as_null_object } + + it 'submits a single writing to the cache' do + cache.write_if_empty + cache.write_if_empty + + expect(backend).to have_received(:write).with(cache.key, + hash_including('CHANGELOG-false-false-false'), + expires_in: 1.week).once + end + end + + describe '#clear' do + let(:backend) { double('backend').as_null_object } + + it 'clears cache' do + cache.clear + + expect(backend).to have_received(:delete).with(cache.key) + end + end +end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 1098a266140..28c34e234f7 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -591,6 +591,10 @@ describe Gitlab::Git::Repository, :seed_helper do expect(repository.find_remote_root_ref('origin')).to eq 'master' end + it 'returns UTF-8' do + expect(repository.find_remote_root_ref('origin')).to be_utf8 + end + it 'returns nil when remote name is nil' do expect_any_instance_of(Gitlab::GitalyClient::RemoteService) .not_to receive(:find_remote_root_ref) diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 54f2ea33f90..bcdf12a00a0 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -19,7 +19,14 @@ describe Gitlab::GitalyClient::CommitService do right_commit_id: commit.id, collapse_diffs: false, enforce_limits: true, - **Gitlab::Git::DiffCollection.collection_limits.to_h + # Tests limitation parameters explicitly + max_files: 100, + max_lines: 5000, + max_bytes: 512000, + safe_max_files: 100, + safe_max_lines: 5000, + safe_max_bytes: 512000, + max_patch_bytes: 102400 ) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) @@ -37,7 +44,14 @@ describe Gitlab::GitalyClient::CommitService do right_commit_id: initial_commit.id, collapse_diffs: false, enforce_limits: true, - **Gitlab::Git::DiffCollection.collection_limits.to_h + # Tests limitation parameters explicitly + max_files: 100, + max_lines: 5000, + max_bytes: 512000, + safe_max_files: 100, + safe_max_lines: 5000, + safe_max_bytes: 512000, + max_patch_bytes: 102400 ) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb index b8831c54aba..9030a49983d 100644 --- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb @@ -54,6 +54,15 @@ describe Gitlab::GitalyClient::RemoteService do expect(client.find_remote_root_ref('origin')).to eq 'master' end + + it 'ensure ref is a valid UTF-8 string' do + expect_any_instance_of(Gitaly::RemoteService::Stub) + .to receive(:find_remote_root_ref) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(double(ref: "an_invalid_ref_\xE5")) + + expect(client.find_remote_root_ref('origin')).to eq "an_invalid_ref_Ã¥" + end end describe '#update_remote_mirror' do diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb deleted file mode 100644 index 5059d68e54b..00000000000 --- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do - let!(:service) { described_class.new } - let!(:project) { create(:project, :with_object_export) } - let(:shared) { project.import_export_shared } - let!(:user) { create(:user) } - - describe '#execute' do - before do - allow(service).to receive(:strategy_execute) - stub_feature_flags(import_export_object_storage: true) - end - - it 'returns if project exported file is not found' do - allow(project).to receive(:export_project_object_exists?).and_return(false) - - expect(service).not_to receive(:strategy_execute) - - service.execute(user, project) - end - - it 'creates a lock file in the export dir' do - allow(service).to receive(:delete_after_export_lock) - - service.execute(user, project) - - expect(lock_path_exist?).to be_truthy - end - - context 'when the method succeeds' do - it 'removes the lock file' do - service.execute(user, project) - - expect(lock_path_exist?).to be_falsey - end - end - - context 'when the method fails' do - before do - allow(service).to receive(:strategy_execute).and_call_original - end - - context 'when validation fails' do - before do - allow(service).to receive(:invalid?).and_return(true) - end - - it 'does not create the lock file' do - expect(service).not_to receive(:create_or_update_after_export_lock) - - service.execute(user, project) - end - - it 'does not execute main logic' do - expect(service).not_to receive(:strategy_execute) - - service.execute(user, project) - end - - it 'logs validation errors in shared context' do - expect(service).to receive(:log_validation_errors) - - service.execute(user, project) - end - end - - context 'when an exception is raised' do - it 'removes the lock' do - expect { service.execute(user, project) }.to raise_error(NotImplementedError) - - expect(lock_path_exist?).to be_falsey - end - end - end - end - - describe '#log_validation_errors' do - it 'add the message to the shared context' do - errors = %w(test_message test_message2) - - allow(service).to receive(:invalid?).and_return(true) - allow(service.errors).to receive(:full_messages).and_return(errors) - - expect(shared).to receive(:add_error_message).twice.and_call_original - - service.execute(user, project) - - expect(shared.errors).to eq errors - end - end - - describe '#to_json' do - it 'adds the current strategy class to the serialized attributes' do - params = { param1: 1 } - result = params.merge(klass: described_class.to_s).to_json - - expect(described_class.new(params).to_json).to eq result - end - end - - def lock_path_exist? - File.exist?(described_class.lock_file_path(project)) - end -end diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb index 566b7f46c87..9a442de2900 100644 --- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb +++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb @@ -9,11 +9,10 @@ describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do describe '#execute' do before do allow(service).to receive(:strategy_execute) - stub_feature_flags(import_export_object_storage: false) end it 'returns if project exported file is not found' do - allow(project).to receive(:export_project_path).and_return(nil) + allow(project).to receive(:export_file_exists?).and_return(false) expect(service).not_to receive(:strategy_execute) diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb index 7f2e0a4ee2c..ec17ad8541f 100644 --- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb +++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb @@ -24,34 +24,13 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do end describe '#execute' do - context 'without object storage' do - before do - stub_feature_flags(import_export_object_storage: false) - end - - it 'removes the exported project file after the upload' do - allow(strategy).to receive(:send_file) - allow(strategy).to receive(:handle_response_error) - - expect(project).to receive(:remove_exported_project_file) - - strategy.execute(user, project) - end - end - - context 'with object storage' do - before do - stub_feature_flags(import_export_object_storage: true) - end + it 'removes the exported project file after the upload' do + allow(strategy).to receive(:send_file) + allow(strategy).to receive(:handle_response_error) - it 'removes the exported project file after the upload' do - allow(strategy).to receive(:send_file) - allow(strategy).to receive(:handle_response_error) + expect(project).to receive(:remove_exports) - expect(project).to receive(:remove_exported_project_file) - - strategy.execute(user, project) - end + strategy.execute(user, project) end end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 2412cc3010a..ec2bdbe22e1 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -324,3 +324,9 @@ metrics: - latest_closed_by - merged_by - pipeline +resource_label_events: +- user +- issue +- merge_request +- epic +- label diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index 90e6d653d34..2bd1b9924c6 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -8,8 +8,7 @@ describe Gitlab::ImportExport::AvatarSaver do before do FileUtils.mkdir_p("#{shared.export_path}/avatar/") - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) - stub_feature_flags(import_export_object_storage: false) + allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:export_path).and_return(export_path) end after do @@ -19,7 +18,7 @@ describe Gitlab::ImportExport::AvatarSaver do it 'saves a project avatar' do described_class.new(project: project_with_avatar, shared: shared).save - expect(File).to exist("#{shared.export_path}/avatar/dk.png") + expect(File).to exist(Dir["#{shared.export_path}/avatar/**/dk.png"].first) end it 'is fine not to have an avatar' do diff --git a/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb b/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb deleted file mode 100644 index 287745eb40e..00000000000 --- a/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ImportExport::FileImporter do - let(:shared) { Gitlab::ImportExport::Shared.new(nil) } - let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" } - let(:valid_file) { "#{shared.export_path}/valid.json" } - let(:symlink_file) { "#{shared.export_path}/invalid.json" } - let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" } - let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" } - let(:evil_symlink_file) { "#{shared.export_path}/.\nevil" } - - before do - stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(FileUploader) - - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(storage_path) - allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true) - allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test') - allow(SecureRandom).to receive(:hex).and_return('abcd') - setup_files - end - - after do - FileUtils.rm_rf(storage_path) - end - - context 'normal run' do - before do - described_class.import(project: build(:project), archive_file: '', shared: shared) - end - - it 'removes symlinks in root folder' do - expect(File.exist?(symlink_file)).to be false - end - - it 'removes hidden symlinks in root folder' do - expect(File.exist?(hidden_symlink_file)).to be false - end - - it 'removes evil symlinks in root folder' do - expect(File.exist?(evil_symlink_file)).to be false - end - - it 'removes symlinks in subfolders' do - expect(File.exist?(subfolder_symlink_file)).to be false - end - - it 'does not remove a valid file' do - expect(File.exist?(valid_file)).to be true - end - - it 'creates the file in the right subfolder' do - expect(shared.export_path).to include('test/abcd') - end - end - - context 'error' do - before do - allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError) - described_class.import(project: build(:project), archive_file: '', shared: shared) - end - - it 'removes symlinks in root folder' do - expect(File.exist?(symlink_file)).to be false - end - - it 'removes hidden symlinks in root folder' do - expect(File.exist?(hidden_symlink_file)).to be false - end - - it 'removes symlinks in subfolders' do - expect(File.exist?(subfolder_symlink_file)).to be false - end - - it 'does not remove a valid file' do - expect(File.exist?(valid_file)).to be true - end - end - - def setup_files - FileUtils.mkdir_p("#{shared.export_path}/subfolder/") - FileUtils.touch(valid_file) - FileUtils.ln_s(valid_file, symlink_file) - FileUtils.ln_s(valid_file, subfolder_symlink_file) - FileUtils.ln_s(valid_file, hidden_symlink_file) - FileUtils.ln_s(valid_file, evil_symlink_file) - end -end diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb index 78fccdf1dfc..bf34cefe18f 100644 --- a/spec/lib/gitlab/import_export/file_importer_spec.rb +++ b/spec/lib/gitlab/import_export/file_importer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::FileImporter do let(:shared) { Gitlab::ImportExport::Shared.new(nil) } - let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" } + let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" } let(:valid_file) { "#{shared.export_path}/valid.json" } let(:symlink_file) { "#{shared.export_path}/invalid.json" } let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" } @@ -11,7 +11,9 @@ describe Gitlab::ImportExport::FileImporter do before do stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + stub_uploads_object_storage(FileUploader) + + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(storage_path) allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true) allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test') allow(SecureRandom).to receive(:hex).and_return('abcd') @@ -19,12 +21,12 @@ describe Gitlab::ImportExport::FileImporter do end after do - FileUtils.rm_rf(export_path) + FileUtils.rm_rf(storage_path) end context 'normal run' do before do - described_class.import(project: nil, archive_file: '', shared: shared) + described_class.import(project: build(:project), archive_file: '', shared: shared) end it 'removes symlinks in root folder' do @@ -55,7 +57,7 @@ describe Gitlab::ImportExport::FileImporter do context 'error' do before do allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError) - described_class.import(project: nil, archive_file: '', shared: shared) + described_class.import(project: build(:project), archive_file: '', shared: shared) end it 'removes symlinks in root folder' do diff --git a/spec/lib/gitlab/import_export/importer_object_storage_spec.rb b/spec/lib/gitlab/import_export/importer_object_storage_spec.rb deleted file mode 100644 index 24a994b3611..00000000000 --- a/spec/lib/gitlab/import_export/importer_object_storage_spec.rb +++ /dev/null @@ -1,115 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ImportExport::Importer do - let(:user) { create(:user) } - let(:test_path) { "#{Dir.tmpdir}/importer_spec" } - let(:shared) { project.import_export_shared } - let(:project) { create(:project) } - let(:import_file) { fixture_file_upload('spec/features/projects/import_export/test_project_export.tar.gz') } - - subject(:importer) { described_class.new(project) } - - before do - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path) - allow_any_instance_of(Gitlab::ImportExport::FileImporter).to receive(:remove_import_file) - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(FileUploader) - - FileUtils.mkdir_p(shared.export_path) - ImportExportUpload.create(project: project, import_file: import_file) - end - - after do - FileUtils.rm_rf(test_path) - end - - describe '#execute' do - it 'succeeds' do - importer.execute - - expect(shared.errors).to be_empty - end - - it 'extracts the archive' do - expect(Gitlab::ImportExport::FileImporter).to receive(:import).and_call_original - - importer.execute - end - - it 'checks the version' do - expect(Gitlab::ImportExport::VersionChecker).to receive(:check!).and_call_original - - importer.execute - end - - context 'all restores are executed' do - [ - Gitlab::ImportExport::AvatarRestorer, - Gitlab::ImportExport::RepoRestorer, - Gitlab::ImportExport::WikiRestorer, - Gitlab::ImportExport::UploadsRestorer, - Gitlab::ImportExport::LfsRestorer, - Gitlab::ImportExport::StatisticsRestorer - ].each do |restorer| - it "calls the #{restorer}" do - fake_restorer = double(restorer.to_s) - - expect(fake_restorer).to receive(:restore).and_return(true).at_least(1) - expect(restorer).to receive(:new).and_return(fake_restorer).at_least(1) - - importer.execute - end - end - - it 'restores the ProjectTree' do - expect(Gitlab::ImportExport::ProjectTreeRestorer).to receive(:new).and_call_original - - importer.execute - end - - it 'removes the import file' do - expect(importer).to receive(:remove_import_file).and_call_original - - importer.execute - - expect(project.import_export_upload.import_file&.file).to be_nil - end - end - - context 'when project successfully restored' do - let!(:existing_project) { create(:project, namespace: user.namespace) } - let(:project) { create(:project, namespace: user.namespace, name: 'whatever', path: 'whatever') } - - before do - restorers = double(:restorers, all?: true) - - allow(subject).to receive(:import_file).and_return(true) - allow(subject).to receive(:check_version!).and_return(true) - allow(subject).to receive(:restorers).and_return(restorers) - allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path })) - end - - context 'when import_data' do - context 'has original_path' do - it 'overwrites existing project' do - expect_any_instance_of(::Projects::OverwriteProjectService).to receive(:execute).with(existing_project) - - subject.execute - end - end - - context 'has not original_path' do - before do - allow(project).to receive(:import_data).and_return(double(data: {})) - end - - it 'does not call the overwrite service' do - expect_any_instance_of(::Projects::OverwriteProjectService).not_to receive(:execute).with(existing_project) - - subject.execute - end - end - end - end - end -end diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb index 8053c48ad6c..11f98d782b1 100644 --- a/spec/lib/gitlab/import_export/importer_spec.rb +++ b/spec/lib/gitlab/import_export/importer_spec.rb @@ -4,16 +4,18 @@ describe Gitlab::ImportExport::Importer do let(:user) { create(:user) } let(:test_path) { "#{Dir.tmpdir}/importer_spec" } let(:shared) { project.import_export_shared } - let(:project) { create(:project, import_source: File.join(test_path, 'test_project_export.tar.gz')) } + let(:project) { create(:project) } + let(:import_file) { fixture_file_upload('spec/features/projects/import_export/test_project_export.tar.gz') } subject(:importer) { described_class.new(project) } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path) allow_any_instance_of(Gitlab::ImportExport::FileImporter).to receive(:remove_import_file) + stub_uploads_object_storage(FileUploader) FileUtils.mkdir_p(shared.export_path) - FileUtils.cp(Rails.root.join('spec/features/projects/import_export/test_project_export.tar.gz'), test_path) + ImportExportUpload.create(project: project, import_file: import_file) end after do @@ -64,6 +66,14 @@ describe Gitlab::ImportExport::Importer do importer.execute end + it 'removes the import file' do + expect(importer).to receive(:remove_import_file).and_call_original + + importer.execute + + expect(project.import_export_upload.import_file&.file).to be_nil + end + it 'sets the correct visibility_level when visibility level is a string' do project.create_or_update_import_data( data: { override_params: { visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s } } @@ -85,7 +95,6 @@ describe Gitlab::ImportExport::Importer do allow(subject).to receive(:import_file).and_return(true) allow(subject).to receive(:check_version!).and_return(true) allow(subject).to receive(:restorers).and_return(restorers) - allow(restorers).to receive(:all?).and_return(true) allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path })) end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 1b7fa11cb3c..eefd00e7383 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -331,6 +331,28 @@ }, "events": [] } + ], + "resource_label_events": [ + { + "id":244, + "action":"remove", + "issue_id":40, + "merge_request_id":null, + "label_id":2, + "user_id":1, + "created_at":"2018-08-28T08:24:00.494Z", + "label": { + "id": 2, + "title": "test2", + "color": "#428bca", + "project_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "type": "ProjectLabel" + } + } ] }, { @@ -2515,6 +2537,17 @@ "events": [] } ], + "resource_label_events": [ + { + "id":243, + "action":"add", + "issue_id":null, + "merge_request_id":27, + "label_id":null, + "user_id":1, + "created_at":"2018-08-28T08:24:00.494Z" + } + ], "merge_request_diff": { "id": 27, "state": "collected", diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index a88ac0a091e..3ff6be595a8 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -89,6 +89,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(ProtectedTag.first.create_access_levels).not_to be_empty end + it 'restores issue resource label events' do + expect(Issue.find_by(title: 'Voluptatem').resource_label_events).not_to be_empty + end + + it 'restores merge requests resource label events' do + expect(MergeRequest.find_by(title: 'MR1').resource_label_events).not_to be_empty + end + context 'event at forth level of the tree' do let(:event) { Event.where(action: 6).first } diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index fec8a2af9ab..5dc372263ad 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -169,6 +169,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver do expect(priorities.flatten).not_to be_empty end + it 'has issue resource label events' do + expect(saved_project_json['issues'].first['resource_label_events']).not_to be_empty + end + + it 'has merge request resource label events' do + expect(saved_project_json['merge_requests'].first['resource_label_events']).not_to be_empty + end + it 'saves the correct service type' do expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService') end @@ -291,6 +299,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do project: project, commit_id: ci_build.pipeline.sha) + create(:resource_label_event, label: project_label, issue: issue) + create(:resource_label_event, label: group_label, merge_request: merge_request) + create(:event, :created, target: milestone, project: project, author: user) create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' }) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 1be448b0486..e9f1be172b0 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -579,3 +579,11 @@ Badge: - type ProjectCiCdSetting: - group_runners_enabled +ResourceLabelEvent: +- id +- action +- issue_id +- merge_request_id +- label_id +- user_id +- created_at diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb index 02f1a4b81aa..d185ff2dfcc 100644 --- a/spec/lib/gitlab/import_export/saver_spec.rb +++ b/spec/lib/gitlab/import_export/saver_spec.rb @@ -18,26 +18,12 @@ describe Gitlab::ImportExport::Saver do FileUtils.rm_rf(export_path) end - context 'local archive' do - it 'saves the repo to disk' do - stub_feature_flags(import_export_object_storage: false) + it 'saves the repo using object storage' do + stub_uploads_object_storage(ImportExportUploader) - subject.save + subject.save - expect(shared.errors).to be_empty - expect(Dir.empty?(shared.archive_path)).to be false - end - end - - context 'object storage' do - it 'saves the repo using object storage' do - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(ImportExportUploader) - - subject.save - - expect(ImportExportUpload.find_by(project: project).export_file.url) - .to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*]) - end + expect(ImportExportUpload.find_by(project: project).export_file.url) + .to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*]) end end diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb index f799de18cd0..792117e1df1 100644 --- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::ImportExport::UploadsManager do let(:shared) { project.import_export_shared } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:project) { create(:project) } + let(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) } let(:exported_file_path) { "#{shared.export_path}/uploads/#{upload.secret}/#{File.basename(upload.path)}" } subject(:manager) { described_class.new(project: project, shared: shared) } @@ -69,44 +70,20 @@ describe Gitlab::ImportExport::UploadsManager do end end end + end - context 'using object storage' do - let!(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) } - - before do - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(FileUploader) - end - - it 'saves the file' do - fake_uri = double - - expect(fake_uri).to receive(:open).and_return(StringIO.new('File content')) - expect(URI).to receive(:parse).and_return(fake_uri) - - manager.save + describe '#restore' do + before do + stub_uploads_object_storage(FileUploader) - expect(File.read(exported_file_path)).to eq('File content') - end + FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038')) + FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt")) end - describe '#restore' do - context 'using object storage' do - before do - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(FileUploader) - - FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038')) - FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt")) - end + it 'restores the file' do + manager.restore - it 'restores the file' do - manager.restore - - expect(project.uploads.size).to eq(1) - expect(project.uploads.first.build_uploader.filename).to eq('dummy.txt') - end - end + expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt') end end end diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb index acef97459b8..6072f18b8c7 100644 --- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::ImportExport::UploadsRestorer do before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/random')) - FileUtils.touch(File.join(shared.export_path, 'uploads/random', "dummy.txt")) + FileUtils.touch(File.join(shared.export_path, 'uploads/random', 'dummy.txt')) end after do @@ -27,9 +27,7 @@ describe Gitlab::ImportExport::UploadsRestorer do it 'copies the uploads to the project path' do subject.restore - uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) } - - expect(uploads).to include('dummy.txt') + expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt') end end @@ -45,9 +43,7 @@ describe Gitlab::ImportExport::UploadsRestorer do it 'copies the uploads to the project path' do subject.restore - uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) } - - expect(uploads).to include('dummy.txt') + expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt') end end end diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb index c716edd9397..24993460e51 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -7,7 +7,6 @@ describe Gitlab::ImportExport::UploadsSaver do let(:shared) { project.import_export_shared } before do - stub_feature_flags(import_export_object_storage: false) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 23869f3d2da..b3f55a2e1bd 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -336,6 +336,22 @@ describe Gitlab::Workhorse do it { expect { subject }.to raise_exception('Unsupported action: download') } end end + + context 'when receive_max_input_size has been updated' do + it 'returns custom git config' do + allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { 1 } + + expect(subject[:GitConfigOptions]).to be_present + end + end + + context 'when receive_max_input_size is empty' do + it 'returns an empty git config' do + allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { nil } + + expect(subject[:GitConfigOptions]).to be_empty + end + end end describe '.set_key_and_notify' do diff --git a/spec/migrations/remove_orphaned_label_links_spec.rb b/spec/migrations/remove_orphaned_label_links_spec.rb new file mode 100644 index 00000000000..13b8919343e --- /dev/null +++ b/spec/migrations/remove_orphaned_label_links_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180906051323_remove_orphaned_label_links.rb') + +describe RemoveOrphanedLabelLinks, :migration do + let(:label_links) { table(:label_links) } + let(:labels) { table(:labels) } + + let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs + let(:label) { create_label } + + context 'add foreign key on label_id' do + let!(:label_link_with_label) { create_label_link(label_id: label.id) } + let!(:label_link_without_label) { create_label_link(label_id: nil) } + + it 'removes orphaned labels without corresponding label' do + expect { migrate! }.to change { LabelLink.count }.from(2).to(1) + end + + it 'does not remove entries with valid label_id' do + expect { migrate! }.not_to change { label_link_with_label.reload } + end + end + + def create_label(**opts) + labels.create!( + project_id: project.id, + **opts + ) + end + + def create_label_link(**opts) + label_links.create!( + target_id: 1, + target_type: 'Issue', + **opts + ) + end +end diff --git a/spec/models/label_note_spec.rb b/spec/models/label_note_spec.rb new file mode 100644 index 00000000000..f69874d94aa --- /dev/null +++ b/spec/models/label_note_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe LabelNote do + set(:project) { create(:project, :repository) } + set(:user) { create(:user) } + set(:label) { create(:label, project: project) } + set(:label2) { create(:label, project: project) } + let(:resource_parent) { project } + + context 'when resource is issue' do + set(:resource) { create(:issue, project: project) } + + it_behaves_like 'label note created from events' + end + + context 'when resource is merge request' do + set(:resource) { create(:merge_request, source_project: project, target_project: project) } + + it_behaves_like 'label note created from events' + end +end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 9b7f932ec3a..3649990670b 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -394,12 +394,6 @@ describe Namespace do child.destroy end end - - it 'removes the exports folder' do - expect(namespace).to receive(:remove_exports!) - - namespace.destroy - end end context 'hashed storage' do @@ -414,12 +408,6 @@ describe Namespace do expect(File.exist?(deleted_path_in_dir)).to be(false) end - - it 'removes the exports folder' do - expect(namespace).to receive(:remove_exports!) - - namespace.destroy - end end end @@ -706,26 +694,6 @@ describe Namespace do end end - describe '#remove_exports' do - let(:legacy_project) { create(:project, :with_export, :legacy_storage, namespace: namespace) } - let(:hashed_project) { create(:project, :with_export, namespace: namespace) } - let(:export_path) { Dir.mktmpdir('namespace_remove_exports_spec') } - let(:legacy_export) { legacy_project.export_project_path } - let(:hashed_export) { hashed_project.export_project_path } - - it 'removes exports for legacy and hashed projects' do - allow(Gitlab::ImportExport).to receive(:storage_path) { export_path } - - expect(File.exist?(legacy_export)).to be_truthy - expect(File.exist?(hashed_export)).to be_truthy - - namespace.remove_exports! - - expect(File.exist?(legacy_export)).to be_falsy - expect(File.exist?(hashed_export)).to be_falsy - end - end - describe '#full_path_was' do context 'when the group has no parent' do it 'should return the path was' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 264632dba4b..cb844cd2102 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2854,73 +2854,12 @@ describe Project do end describe '#remove_export' do - let(:legacy_project) { create(:project, :legacy_storage, :with_export) } let(:project) { create(:project, :with_export) } - before do - stub_feature_flags(import_export_object_storage: false) - end - - it 'removes the exports directory for the project' do - expect(File.exist?(project.export_path)).to be_truthy - - allow(FileUtils).to receive(:rm_rf).and_call_original - expect(FileUtils).to receive(:rm_rf).with(project.export_path).and_call_original + it 'removes the export' do project.remove_exports - expect(File.exist?(project.export_path)).to be_falsy - end - - it 'is a no-op on legacy projects when there is no namespace' do - export_path = legacy_project.export_path - - legacy_project.namespace.delete - legacy_project.reload - - expect(FileUtils).not_to receive(:rm_rf).with(export_path) - - legacy_project.remove_exports - - expect(File.exist?(export_path)).to be_truthy - end - - it 'runs on hashed storage projects when there is no namespace' do - export_path = project.export_path - - project.namespace.delete - legacy_project.reload - - allow(FileUtils).to receive(:rm_rf).and_call_original - expect(FileUtils).to receive(:rm_rf).with(export_path).and_call_original - - project.remove_exports - - expect(File.exist?(export_path)).to be_falsy - end - - it 'is run when the project is destroyed' do - expect(project).to receive(:remove_exports).and_call_original - - project.destroy - end - end - - describe '#remove_exported_project_file' do - let(:project) { create(:project, :with_export) } - - it 'removes the exported project file' do - stub_feature_flags(import_export_object_storage: false) - - exported_file = project.export_project_path - - expect(File.exist?(exported_file)).to be_truthy - - allow(FileUtils).to receive(:rm_rf).and_call_original - expect(FileUtils).to receive(:rm_rf).with(exported_file).and_call_original - - project.remove_exported_project_file - - expect(File.exist?(exported_file)).to be_falsy + expect(project.export_file_exists?).to be_falsey end end diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb index 4756caa1b97..da6e1b5610d 100644 --- a/spec/models/resource_label_event_spec.rb +++ b/spec/models/resource_label_event_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe ResourceLabelEvent, type: :model do - subject { build(:resource_label_event) } + subject { build(:resource_label_event, issue: issue) } let(:issue) { create(:issue) } let(:merge_request) { create(:merge_request) } @@ -16,8 +16,6 @@ RSpec.describe ResourceLabelEvent, type: :model do describe 'validations' do it { is_expected.to be_valid } - it { is_expected.to validate_presence_of(:label) } - it { is_expected.to validate_presence_of(:user) } describe 'Issuable validation' do it 'is invalid if issue_id and merge_request_id are missing' do @@ -45,4 +43,52 @@ RSpec.describe ResourceLabelEvent, type: :model do end end end + + describe '#expire_etag_cache' do + def expect_expiration(issue) + expect_any_instance_of(Gitlab::EtagCaching::Store) + .to receive(:touch) + .with("/#{issue.project.namespace.to_param}/#{issue.project.to_param}/noteable/issue/#{issue.id}/notes") + end + + it 'expires resource note etag cache on event save' do + expect_expiration(subject.issuable) + + subject.save! + end + + it 'expires resource note etag cache on event destroy' do + subject.save! + + expect_expiration(subject.issuable) + + subject.destroy! + end + end + + describe '#outdated_markdown?' do + it 'returns true if label is missing and reference is not empty' do + subject.attributes = { reference: 'ref', label_id: nil } + + expect(subject.outdated_markdown?).to be true + end + + it 'returns true if reference is not set yet' do + subject.attributes = { reference: nil } + + expect(subject.outdated_markdown?).to be true + end + + it 'returns true markdown is outdated' do + subject.attributes = { cached_markdown_version: 0 } + + expect(subject.outdated_markdown?).to be true + end + + it 'returns false if label and reference are set' do + subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION } + + expect(subject.outdated_markdown?).to be false + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 2a7aff39240..bee4a3d24a7 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2957,6 +2957,48 @@ describe User do end end + describe '#requires_usage_stats_consent?' do + let(:user) { create(:user, created_at: 8.days.ago) } + + before do + allow(user).to receive(:has_current_license?).and_return false + end + + context 'in single-user environment' do + it 'requires user consent after one week' do + create(:user, ghost: true) + + expect(user.requires_usage_stats_consent?).to be true + end + + it 'requires user consent after one week if there is another ghost user' do + expect(user.requires_usage_stats_consent?).to be true + end + + it 'does not require consent in the first week' do + user.created_at = 6.days.ago + + expect(user.requires_usage_stats_consent?).to be false + end + + it 'does not require consent if usage stats were set by this user' do + allow(Gitlab::CurrentSettings).to receive(:usage_stats_set_by_user_id).and_return(user.id) + + expect(user.requires_usage_stats_consent?).to be false + end + end + + context 'in multi-user environment' do + before do + create(:user) + end + + it 'does not require consent' do + expect(user.requires_usage_stats_consent?).to be false + end + end + end + context 'with uploads' do it_behaves_like 'model with mounted uploader', false do let(:model_object) { create(:user, :with_avatar) } diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 6890f46c724..e0b5b34f9c4 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -369,6 +369,26 @@ describe API::Internal do expect(user.reload.last_activity_on).to be_nil end end + + context 'when receive_max_input_size has been updated' do + it 'returns custom git config' do + allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { 1 } + + push(key, project) + + expect(json_response["git_config_options"]).to be_present + end + end + + context 'when receive_max_input_size is empty' do + it 'returns an empty git config' do + allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { nil } + + push(key, project) + + expect(json_response["git_config_options"]).to be_empty + end + end end end diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb index 45e4e35d773..0586025956f 100644 --- a/spec/requests/api/project_export_spec.rb +++ b/spec/requests/api/project_export_spec.rb @@ -4,8 +4,8 @@ describe API::ProjectExport do set(:project) { create(:project) } set(:project_none) { create(:project) } set(:project_started) { create(:project) } - set(:project_finished) { create(:project) } - set(:project_after_export) { create(:project) } + let(:project_finished) { create(:project, :with_export) } + let(:project_after_export) { create(:project, :with_export) } set(:user) { create(:user) } set(:admin) { create(:admin) } @@ -29,13 +29,7 @@ describe API::ProjectExport do # simulate exporting work directory FileUtils.mkdir_p File.join(project_started.export_path, 'securerandom-hex') - # simulate exported - FileUtils.mkdir_p project_finished.export_path - FileUtils.touch File.join(project_finished.export_path, '_export.tar.gz') - # simulate in after export action - FileUtils.mkdir_p project_after_export.export_path - FileUtils.touch File.join(project_after_export.export_path, '_export.tar.gz') FileUtils.touch Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy.lock_file_path(project_after_export) end @@ -191,14 +185,11 @@ describe API::ProjectExport do context 'when upload complete' do before do - FileUtils.rm_rf(project_after_export.export_path) - - if project_after_export.export_project_object_exists? - upload = project_after_export.import_export_upload + project_after_export.remove_exports + end - upload.remove_export_file! - upload.save - end + it 'has removed the export' do + expect(project_after_export.export_file_exists?).to be_falsey end it_behaves_like '404 response' do @@ -273,13 +264,13 @@ describe API::ProjectExport do before do stub_uploads_object_storage(ImportExportUploader) - [project, project_finished, project_after_export].each do |p| - p.add_maintainer(user) + project.add_maintainer(user) + project_finished.add_maintainer(user) + project_after_export.add_maintainer(user) - upload = ImportExportUpload.new(project: p) - upload.export_file = fixture_file_upload('spec/fixtures/project_export.tar.gz', "`/tar.gz") - upload.save! - end + upload = ImportExportUpload.new(project: project) + upload.export_file = fixture_file_upload('spec/fixtures/project_export.tar.gz', "`/tar.gz") + upload.save! end it_behaves_like 'get project download by strategy' diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index bc06f3c3732..c8fa4754810 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -7,7 +7,6 @@ describe API::ProjectImport do let(:namespace) { create(:group) } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) - stub_feature_flags(import_export_object_storage: true) stub_uploads_object_storage(FileUploader) namespace.add_owner(user) diff --git a/spec/requests/api/resource_label_events_spec.rb b/spec/requests/api/resource_label_events_spec.rb new file mode 100644 index 00000000000..b7d4a5152cc --- /dev/null +++ b/spec/requests/api/resource_label_events_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::ResourceLabelEvents do + set(:user) { create(:user) } + set(:project) { create(:project, :public, :repository, namespace: user.namespace) } + set(:private_user) { create(:user) } + + before do + project.add_developer(user) + end + + shared_examples 'resource_label_events API' do |parent_type, eventable_type, id_name| + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events" do + it "returns an array of resource label events" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(event.id) + end + + it "returns a 404 error when eventable id not found" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/12345/resource_label_events", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", private_user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events/:event_id" do + it "returns a resource label event by id" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/#{event.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(event.id) + end + + it "returns a 404 error if resource label event not found" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + end + end + + context 'when eventable is an Issue' do + let(:issue) { create(:issue, project: project, author: user) } + + it_behaves_like 'resource_label_events API', 'projects', 'issues', 'iid' do + let(:parent) { project } + let(:eventable) { issue } + let!(:event) { create(:resource_label_event, issue: issue) } + end + end + + context 'when eventable is a Merge Request' do + let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } + + it_behaves_like 'resource_label_events API', 'projects', 'merge_requests', 'iid' do + let(:parent) { project } + let(:eventable) { merge_request } + let!(:event) { create(:resource_label_event, merge_request: merge_request) } + end + end +end diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb index dcf4503ef9c..fa1a421d528 100644 --- a/spec/services/issuable/common_system_notes_service_spec.rb +++ b/spec/services/issuable/common_system_notes_service_spec.rb @@ -12,12 +12,21 @@ describe Issuable::CommonSystemNotesService do it_behaves_like 'system note creation', { time_estimate: 5 }, 'changed time estimate' context 'when new label is added' do + let(:label) { create(:label, project: project) } + before do - label = create(:label, project: project) issuable.labels << label + issuable.save end - it_behaves_like 'system note creation', {}, /added ~\w+ label/ + it 'creates a resource label event' do + described_class.new(project, user).execute(issuable, []) + event = issuable.reload.resource_label_events.last + + expect(event).not_to be_nil + expect(event.label_id).to eq label.id + expect(event.user_id).to eq user.id + end end context 'when new milestone is assigned' do diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 609eef76d2c..b5767583952 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -122,6 +122,17 @@ describe Issues::MoveService do end end + context 'issue with resource label events' do + it 'assigns resource label events to new issue' do + old_issue.resource_label_events = create_list(:resource_label_event, 2, issue: old_issue) + + new_issue = move_service.execute(old_issue, new_project) + + expected = old_issue.resource_label_events.map(&:label_id) + expect(new_issue.resource_label_events.map(&:label_id)).to match_array(expected) + end + end + context 'generic issue' do include_context 'issue move executed' diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 5bcfef46b75..07aa8449a66 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -189,11 +189,12 @@ describe Issues::UpdateService, :mailer do expect(note.note).to include "assigned to #{user2.to_reference}" end - it 'creates system note about issue label edit' do - note = find_note('added ~') + it 'creates a resource label event' do + event = issue.resource_label_events.last - expect(note).not_to be_nil - expect(note.note).to include "added #{label.to_reference} label" + expect(event).not_to be_nil + expect(event.label_id).to eq label.id + expect(event.user_id).to eq user.id end it 'creates system note about title change' do diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb index a0a27d247fc..21f369a3818 100644 --- a/spec/services/merge_requests/reload_diffs_service_spec.rb +++ b/spec/services/merge_requests/reload_diffs_service_spec.rb @@ -57,6 +57,7 @@ describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_cachin expect(Rails.cache).to receive(:delete).with(old_cache_key).and_call_original expect(Rails.cache).to receive(:read).with(new_cache_key).and_call_original expect(Rails.cache).to receive(:write).with(new_cache_key, anything, anything).and_call_original + subject.execute end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index f0029af83cc..55dfab81c26 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -109,11 +109,12 @@ describe MergeRequests::UpdateService, :mailer do expect(note.note).to include "assigned to #{user2.to_reference}" end - it 'creates system note about merge_request label edit' do - note = find_note('added ~') + it 'creates a resource label event' do + event = merge_request.resource_label_events.last - expect(note).not_to be_nil - expect(note.note).to include "added #{label.to_reference} label" + expect(event).not_to be_nil + expect(event.label_id).to eq label.id + expect(event.user_id).to eq user.id end it 'creates system note about title change' do diff --git a/spec/services/projects/container_repository/destroy_service_spec.rb b/spec/services/projects/container_repository/destroy_service_spec.rb new file mode 100644 index 00000000000..307ccc88865 --- /dev/null +++ b/spec/services/projects/container_repository/destroy_service_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::ContainerRepository::DestroyService do + set(:user) { create(:user) } + set(:project) { create(:project, :private) } + + subject { described_class.new(project, user) } + + before do + stub_container_registry_config(enabled: true) + end + + context 'when user does not have access to registry' do + let!(:repository) { create(:container_repository, :root, project: project) } + + it 'does not delete a repository' do + expect { subject.execute(repository) }.not_to change { ContainerRepository.all.count } + end + end + + context 'when user has access to registry' do + before do + project.add_developer(user) + end + + context 'when root container repository exists' do + let!(:repository) { create(:container_repository, :root, project: project) } + + before do + stub_container_registry_tags(repository: :any, tags: []) + end + + it 'deletes the repository' do + expect { described_class.new(project, user).execute(repository) }.to change { ContainerRepository.all.count }.by(-1) + end + end + end +end diff --git a/spec/services/resource_events/change_labels_service_spec.rb b/spec/services/resource_events/change_labels_service_spec.rb index 41b0fb3eea3..4c9138fb1ef 100644 --- a/spec/services/resource_events/change_labels_service_spec.rb +++ b/spec/services/resource_events/change_labels_service_spec.rb @@ -18,6 +18,14 @@ describe ResourceEvents::ChangeLabelsService do expect(event.action).to eq(action) end + it 'expires resource note etag cache' do + expect_any_instance_of(Gitlab::EtagCaching::Store) + .to receive(:touch) + .with("/#{resource.project.namespace.to_param}/#{resource.project.to_param}/noteable/issue/#{resource.id}/notes") + + described_class.new(resource, author).execute(added_labels: [labels[0]]) + end + context 'when adding a label' do let(:added) { [labels[0]] } let(:removed) { [] } diff --git a/spec/services/resource_events/merge_into_notes_service_spec.rb b/spec/services/resource_events/merge_into_notes_service_spec.rb new file mode 100644 index 00000000000..0d333d541c9 --- /dev/null +++ b/spec/services/resource_events/merge_into_notes_service_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ResourceEvents::MergeIntoNotesService do + def create_event(params) + event_params = { action: :add, label: label, issue: resource, + user: user } + + create(:resource_label_event, event_params.merge(params)) + end + + def create_note(params) + opts = { noteable: resource, project: project } + + create(:note_on_issue, opts.merge(params)) + end + + set(:project) { create(:project) } + set(:user) { create(:user) } + set(:resource) { create(:issue, project: project) } + set(:label) { create(:label, project: project) } + set(:label2) { create(:label, project: project) } + let(:time) { Time.now } + + describe '#execute' do + it 'merges label events into notes in order of created_at' do + note1 = create_note(created_at: 4.days.ago) + note2 = create_note(created_at: 2.days.ago) + event1 = create_event(created_at: 3.days.ago) + event2 = create_event(created_at: 1.day.ago) + + notes = described_class.new(resource, user).execute([note1, note2]) + + expected = [note1, event1, note2, event2].map(&:discussion_id) + expect(notes.map(&:discussion_id)).to eq expected + end + + it 'squashes events with same time and author into single note' do + user2 = create(:user) + + create_event(created_at: time) + create_event(created_at: time, label: label2, action: :remove) + create_event(created_at: time, user: user2) + create_event(created_at: 1.day.ago, label: label2) + + notes = described_class.new(resource, user).execute() + + expected = [ + "added #{label.to_reference} label and removed #{label2.to_reference} label", + "added #{label.to_reference} label", + "added #{label2.to_reference} label" + ] + + expect(notes.count).to eq 3 + expect(notes.map(&:note)).to match_array expected + end + + it 'fetches only notes created after last_fetched_at' do + create_event(created_at: 4.days.ago) + event = create_event(created_at: 1.day.ago) + + notes = described_class.new(resource, user, + last_fetched_at: 2.days.ago.to_i).execute() + + expect(notes.count).to eq 1 + expect(notes.first.discussion_id).to eq event.discussion_id + end + end +end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 48aad8ebdbe..d5d750e182b 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -197,45 +197,6 @@ describe SystemNoteService do end end - describe '.change_label' do - subject { described_class.change_label(noteable, project, author, added, removed) } - - let(:labels) { create_list(:label, 2, project: project) } - let(:added) { [] } - let(:removed) { [] } - - it_behaves_like 'a system note' do - let(:action) { 'label' } - end - - context 'with added labels' do - let(:added) { labels } - let(:removed) { [] } - - it 'sets the note text' do - expect(subject.note).to eq "added ~#{labels[0].id} ~#{labels[1].id} labels" - end - end - - context 'with removed labels' do - let(:added) { [] } - let(:removed) { labels } - - it 'sets the note text' do - expect(subject.note).to eq "removed ~#{labels[0].id} ~#{labels[1].id} labels" - end - end - - context 'with added and removed labels' do - let(:added) { [labels[0]] } - let(:removed) { [labels[1]] } - - it 'sets the note text' do - expect(subject.note).to eq "added ~#{labels[0].id} and removed ~#{labels[1].id} labels" - end - end - end - describe '.change_milestone' do context 'for a project milestone' do subject { described_class.change_milestone(noteable, project, author, milestone) } diff --git a/spec/services/wikis/create_attachment_service_spec.rb b/spec/services/wikis/create_attachment_service_spec.rb index 3f4da873ce4..f5899f292c8 100644 --- a/spec/services/wikis/create_attachment_service_spec.rb +++ b/spec/services/wikis/create_attachment_service_spec.rb @@ -88,8 +88,30 @@ describe Wikis::CreateAttachmentService do end end - describe 'validations' do + describe '#parse_file_name' do context 'when file_name' do + context 'has white spaces' do + let(:file_name) { 'file with spaces' } + + it "replaces all of them with '_'" do + result = service.execute + + expect(result[:status]).to eq :success + expect(result[:result][:file_name]).to eq 'file_with_spaces' + end + end + + context 'has other invalid characters' do + let(:file_name) { "file\twith\tinvalid chars" } + + it "replaces all of them with '_'" do + result = service.execute + + expect(result[:status]).to eq :success + expect(result[:result][:file_name]).to eq 'file_with_invalid_chars' + end + end + context 'is not present' do let(:file_name) { nil } diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb index 4d925ac77f4..d9ed405baf4 100644 --- a/spec/support/import_export/export_file_helper.rb +++ b/spec/support/import_export/export_file_helper.rb @@ -52,7 +52,7 @@ module ExportFileHelper # Expands the compressed file for an exported project into +tmpdir+ def in_directory_with_expanded_export(project) Dir.mktmpdir do |tmpdir| - export_file = project.export_project_path + export_file = project.export_file.path _output, exit_status = Gitlab::Popen.popen(%W{tar -zxf #{export_file} -C #{tmpdir}}) yield(exit_status, tmpdir) diff --git a/spec/support/shared_examples/models/label_note_shared_examples.rb b/spec/support/shared_examples/models/label_note_shared_examples.rb new file mode 100644 index 00000000000..406385c13bd --- /dev/null +++ b/spec/support/shared_examples/models/label_note_shared_examples.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +shared_examples 'label note created from events' do + def create_event(params = {}) + event_params = { action: :add, label: label, user: user } + resource_key = resource.class.name.underscore.to_s + event_params[resource_key] = resource + + build(:resource_label_event, event_params.merge(params)) + end + + def label_refs(events) + labels = events.map(&:label).compact + + labels.map { |l| l.to_reference}.sort.join(' ') + end + + let(:time) { Time.now } + let(:local_label_ids) { [label.id, label2.id] } + + describe '.from_events' do + it 'returns system note with expected attributes' do + event = create_event + + note = described_class.from_events([event, create_event]) + + expect(note.system).to be true + expect(note.author_id).to eq event.user_id + expect(note.discussion_id).to eq event.discussion_id + expect(note.noteable).to eq event.issuable + expect(note.note).to be_present + expect(note.note_html).to be_present + end + + it 'updates markdown cache if reference is not set yet' do + event = create_event(reference: nil) + + described_class.from_events([event]) + + expect(event.reference).not_to be_nil + end + + it 'updates markdown cache if label was deleted' do + event = create_event(reference: 'some_ref', label: nil) + + described_class.from_events([event]) + + expect(event.reference).to eq '' + end + + it 'returns html note' do + events = [create_event(created_at: time)] + + note = described_class.from_events(events) + + expect(note.note_html).to include label.title + end + + it 'returns text note for added labels' do + events = [create_event(created_at: time), + create_event(created_at: time, label: label2), + create_event(created_at: time, label: nil)] + + note = described_class.from_events(events) + + expect(note.note).to eq "added #{label_refs(events)} + 1 deleted label" + end + + it 'returns text note for removed labels' do + events = [create_event(action: :remove, created_at: time), + create_event(action: :remove, created_at: time, label: label2), + create_event(action: :remove, created_at: time, label: nil)] + + note = described_class.from_events(events) + + expect(note.note).to eq "removed #{label_refs(events)} + 1 deleted label" + end + + it 'returns text note for added and removed labels' do + add_events = [create_event(created_at: time), + create_event(created_at: time, label: nil)] + + remove_events = [create_event(action: :remove, created_at: time), + create_event(action: :remove, created_at: time, label: nil)] + + note = described_class.from_events(add_events + remove_events) + + expect(note.note).to eq "added #{label_refs(add_events)} + 1 deleted label and removed #{label_refs(remove_events)} + 1 deleted label" + end + + it 'returns text note for cross-project label' do + other_label = create(:label) + event = create_event(label: other_label) + + note = described_class.from_events([event]) + + expect(note.note).to eq "added #{other_label.to_reference(resource_parent)} label" + end + + it 'returns text note for cross-group label' do + other_label = create(:group_label) + event = create_event(label: other_label) + + note = described_class.from_events([event]) + + expect(note.note).to eq "added #{other_label.to_reference(other_label.group, target_project: project, full: true)} label" + end + end +end diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb index cc2cca10f58..19794227d9f 100644 --- a/spec/tasks/gitlab/cleanup_rake_spec.rb +++ b/spec/tasks/gitlab/cleanup_rake_spec.rb @@ -6,6 +6,8 @@ describe 'gitlab:cleanup rake tasks' do end describe 'cleanup namespaces and repos' do + let(:gitlab_shell) { Gitlab::Shell.new } + let(:storage) { storages.keys.first } let(:storages) do { 'default' => Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/default_storage')) @@ -17,53 +19,56 @@ describe 'gitlab:cleanup rake tasks' do end before do - FileUtils.mkdir(Settings.absolute('tmp/tests/default_storage')) allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) end after do - FileUtils.rm_rf(Settings.absolute('tmp/tests/default_storage')) + Gitlab::GitalyClient::StorageService.new(storage).delete_all_repositories end describe 'cleanup:repos' do before do - FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/broken/project.git')) - FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git')) + gitlab_shell.add_namespace(storage, 'broken/project.git') + gitlab_shell.add_namespace(storage, '@hashed/12/34/5678.git') end it 'moves it to an orphaned path' do - run_rake_task('gitlab:cleanup:repos') - repo_list = Dir['tmp/tests/default_storage/broken/*'] + now = Time.now + + Timecop.freeze(now) do + run_rake_task('gitlab:cleanup:repos') + repo_list = Gitlab::GitalyClient::StorageService.new(storage).list_directories(depth: 0) - expect(repo_list.first).to include('+orphaned+') + expect(repo_list.last).to include("broken+orphaned+#{now.to_i}") + end end it 'ignores @hashed repos' do run_rake_task('gitlab:cleanup:repos') - expect(Dir.exist?(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git'))).to be_truthy + expect(gitlab_shell.exists?(storage, '@hashed/12/34/5678.git')).to be(true) end end describe 'cleanup:dirs' do it 'removes missing namespaces' do - FileUtils.mkdir_p(Settings.absolute("tmp/tests/default_storage/namespace_1/project.git")) - FileUtils.mkdir_p(Settings.absolute("tmp/tests/default_storage/namespace_2/project.git")) - allow(Namespace).to receive(:pluck).and_return('namespace_1') + gitlab_shell.add_namespace(storage, "namespace_1/project.git") + gitlab_shell.add_namespace(storage, "namespace_2/project.git") + allow(Namespace).to receive(:pluck).and_return(['namespace_1']) stub_env('REMOVE', 'true') run_rake_task('gitlab:cleanup:dirs') - expect(Dir.exist?(Settings.absolute('tmp/tests/default_storage/namespace_1'))).to be_truthy - expect(Dir.exist?(Settings.absolute('tmp/tests/default_storage/namespace_2'))).to be_falsey + expect(gitlab_shell.exists?(storage, 'namespace_1')).to be(true) + expect(gitlab_shell.exists?(storage, 'namespace_2')).to be(false) end it 'ignores @hashed directory' do - FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git')) + gitlab_shell.add_namespace(storage, '@hashed/12/34/5678.git') run_rake_task('gitlab:cleanup:dirs') - expect(Dir.exist?(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git'))).to be_truthy + expect(gitlab_shell.exists?(storage, '@hashed/12/34/5678.git')).to be(true) end end end diff --git a/spec/workers/delete_container_repository_worker_spec.rb b/spec/workers/delete_container_repository_worker_spec.rb new file mode 100644 index 00000000000..8c40611a959 --- /dev/null +++ b/spec/workers/delete_container_repository_worker_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe DeleteContainerRepositoryWorker do + let(:registry) { create(:container_repository) } + let(:project) { registry.project } + let(:user) { project.owner } + + subject { described_class.new } + + describe '#perform' do + it 'executes the destroy service' do + service = instance_double(Projects::ContainerRepository::DestroyService) + expect(service).to receive(:execute) + expect(Projects::ContainerRepository::DestroyService).to receive(:new).with(project, user).and_return(service) + + subject.perform(user.id, registry.id) + end + + it 'does not raise error when user could not be found' do + expect do + subject.perform(-1, registry.id) + end.not_to raise_error + end + + it 'does not raise error when registry could not be found' do + expect do + subject.perform(user.id, -1) + end.not_to raise_error + end + end +end |
