diff options
Diffstat (limited to 'spec')
25 files changed, 399 insertions, 204 deletions
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index b45f6f30e40..a81b2169b89 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -28,6 +28,13 @@ FactoryBot.define do secret SecureRandom.hex end + trait :with_file do + after(:create) do |upload| + FileUtils.mkdir_p(File.dirname(upload.absolute_path)) + FileUtils.touch(upload.absolute_path) + end + end + trait :object_storage do store ObjectStorage::Store::REMOTE end diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 38ce92a5dc7..3b741d51598 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -29,8 +29,10 @@ "merge_when_pipeline_succeeds": { "type": "boolean" }, "source_branch": { "type": "string" }, "source_project_id": { "type": "integer" }, + "source_project_full_path": { "type": ["string", "null"]}, "target_branch": { "type": "string" }, "target_project_id": { "type": "integer" }, + "target_project_full_path": { "type": ["string", "null"]}, "allow_collaboration": { "type": "boolean"}, "metrics": { "oneOf": [ diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index 5a2e4b34069..a64f8a11ef2 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -92,11 +92,10 @@ describe SubmoduleHelper do context 'in-repository submodule' do let(:group) { create(:group, name: "Master Project", path: "master-project") } let(:project) { create(:project, group: group) } - before do - self.instance_variable_set(:@project, project) - end it 'in-repository' do + allow(repo).to receive(:project).and_return(project) + stub_url('./') expect(submodule_links(submodule_item)).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/tree/hash"]) end @@ -167,32 +166,28 @@ describe SubmoduleHelper do let(:project) { create(:project, group: group) } let(:commit_id) { sample_commit[:id] } - before do - self.instance_variable_set(:@project, project) - end - it 'one level down' do - result = relative_self_links('../test.git', commit_id) + result = relative_self_links('../test.git', commit_id, project) expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end it 'with trailing whitespace' do - result = relative_self_links('../test.git ', commit_id) + result = relative_self_links('../test.git ', commit_id, project) expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end it 'two levels down' do - result = relative_self_links('../../test.git', commit_id) + result = relative_self_links('../../test.git', commit_id, project) expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end it 'one level down with namespace and repo' do - result = relative_self_links('../foobar/test.git', commit_id) + result = relative_self_links('../foobar/test.git', commit_id, project) expect(result).to eq(["/foobar/test", "/foobar/test/tree/#{commit_id}"]) end it 'two levels down with namespace and repo' do - result = relative_self_links('../foobar/baz/test.git', commit_id) + result = relative_self_links('../foobar/baz/test.git', commit_id, project) expect(result).to eq(["/baz/test", "/baz/test/tree/#{commit_id}"]) end @@ -201,7 +196,7 @@ describe SubmoduleHelper do let(:project) { create(:project, namespace: user.namespace) } it 'one level down with personal project' do - result = relative_self_links('../test.git', commit_id) + result = relative_self_links('../test.git', commit_id, project) expect(result).to eq(["/#{user.username}/test", "/#{user.username}/test/tree/#{commit_id}"]) end end diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js index 7a34126eef7..0b933dda431 100644 --- a/spec/javascripts/environments/environment_item_spec.js +++ b/spec/javascripts/environments/environment_item_spec.js @@ -104,7 +104,7 @@ describe('Environment item', () => { }, ], }, - 'stop_action?': true, + has_stop_action: true, environment_path: 'root/ci-folders/environments/31', created_at: '2016-11-07T11:11:16.525Z', updated_at: '2016-11-10T15:55:58.778Z', diff --git a/spec/javascripts/environments/mock_data.js b/spec/javascripts/environments/mock_data.js index 8a1e26935d9..7bb57f938b8 100644 --- a/spec/javascripts/environments/mock_data.js +++ b/spec/javascripts/environments/mock_data.js @@ -7,7 +7,7 @@ export const environmentsList = [ external_url: null, environment_type: null, last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/7', stop_path: '/root/review-app/environments/7/stop', created_at: '2017-01-31T10:53:46.894Z', @@ -22,7 +22,7 @@ export const environmentsList = [ external_url: null, environment_type: 'build', last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/12', stop_path: '/root/review-app/environments/12/stop', created_at: '2017-02-01T19:42:18.400Z', @@ -41,7 +41,7 @@ export const serverData = [ external_url: null, environment_type: null, last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/7', stop_path: '/root/review-app/environments/7/stop', created_at: '2017-01-31T10:53:46.894Z', @@ -58,7 +58,7 @@ export const serverData = [ external_url: null, environment_type: 'build', last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/12', stop_path: '/root/review-app/environments/12/stop', created_at: '2017-02-01T19:42:18.400Z', @@ -77,7 +77,7 @@ export const environment = { external_url: null, environment_type: null, last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/7', stop_path: '/root/review-app/environments/7/stop', created_at: '2017-01-31T10:53:46.894Z', @@ -95,7 +95,7 @@ export const folder = { external_url: null, environment_type: 'build', last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/12', stop_path: '/root/review-app/environments/12/stop', created_at: '2017-02-01T19:42:18.400Z', diff --git a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js b/spec/javascripts/performance_bar/components/performance_bar_app_spec.js index 9ab9ab1c9f4..7926db44429 100644 --- a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js +++ b/spec/javascripts/performance_bar/components/performance_bar_app_spec.js @@ -1,39 +1,15 @@ import Vue from 'vue'; -import axios from '~/lib/utils/axios_utils'; import performanceBarApp from '~/performance_bar/components/performance_bar_app.vue'; -import PerformanceBarService from '~/performance_bar/services/performance_bar_service'; import PerformanceBarStore from '~/performance_bar/stores/performance_bar_store'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import MockAdapter from 'axios-mock-adapter'; -describe('performance bar', () => { - let mock; +describe('performance bar app', () => { let vm; beforeEach(() => { const store = new PerformanceBarStore(); - mock = new MockAdapter(axios); - - mock.onGet('/-/peek/results').reply( - 200, - { - data: { - gc: { - invokes: 0, - invoke_time: '0.00', - use_size: 0, - total_size: 0, - total_object: 0, - gc_time: '0.00', - }, - host: { hostname: 'web-01' }, - }, - }, - {}, - ); - vm = mountComponent(Vue.extend(performanceBarApp), { store, env: 'development', @@ -45,44 +21,9 @@ describe('performance bar', () => { afterEach(() => { vm.$destroy(); - mock.restore(); }); it('sets the class to match the environment', () => { expect(vm.$el.getAttribute('class')).toContain('development'); }); - - describe('loadRequestDetails', () => { - beforeEach(() => { - spyOn(vm.store, 'addRequest').and.callThrough(); - }); - - it('does nothing if the request cannot be tracked', () => { - spyOn(vm.store, 'canTrackRequest').and.callFake(() => false); - - vm.loadRequestDetails('123', 'https://gitlab.com/'); - - expect(vm.store.addRequest).not.toHaveBeenCalled(); - }); - - it('adds the request immediately', () => { - vm.loadRequestDetails('123', 'https://gitlab.com/'); - - expect(vm.store.addRequest).toHaveBeenCalledWith( - '123', - 'https://gitlab.com/', - ); - }); - - it('makes an HTTP request for the request details', () => { - spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough(); - - vm.loadRequestDetails('456', 'https://gitlab.com/'); - - expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith( - '/-/peek/results', - '456', - ); - }); - }); }); diff --git a/spec/javascripts/performance_bar/index_spec.js b/spec/javascripts/performance_bar/index_spec.js new file mode 100644 index 00000000000..1784bd64adb --- /dev/null +++ b/spec/javascripts/performance_bar/index_spec.js @@ -0,0 +1,83 @@ +import axios from '~/lib/utils/axios_utils'; +import performanceBar from '~/performance_bar'; +import PerformanceBarService from '~/performance_bar/services/performance_bar_service'; + +import MockAdapter from 'axios-mock-adapter'; + +describe('performance bar wrapper', () => { + let mock; + let vm; + + beforeEach(() => { + const peekWrapper = document.createElement('div'); + + peekWrapper.setAttribute('id', 'js-peek'); + peekWrapper.setAttribute('data-env', 'development'); + peekWrapper.setAttribute('data-request-id', '123'); + peekWrapper.setAttribute('data-peek-url', '/-/peek/results'); + peekWrapper.setAttribute('data-profile-url', '?lineprofiler=true'); + + document.body.appendChild(peekWrapper); + + mock = new MockAdapter(axios); + + mock.onGet('/-/peek/results').reply( + 200, + { + data: { + gc: { + invokes: 0, + invoke_time: '0.00', + use_size: 0, + total_size: 0, + total_object: 0, + gc_time: '0.00', + }, + host: { hostname: 'web-01' }, + }, + }, + {}, + ); + + vm = performanceBar({ container: '#js-peek' }); + }); + + afterEach(() => { + vm.$destroy(); + mock.restore(); + }); + + describe('loadRequestDetails', () => { + beforeEach(() => { + spyOn(vm.store, 'addRequest').and.callThrough(); + }); + + it('does nothing if the request cannot be tracked', () => { + spyOn(vm.store, 'canTrackRequest').and.callFake(() => false); + + vm.loadRequestDetails('123', 'https://gitlab.com/'); + + expect(vm.store.addRequest).not.toHaveBeenCalled(); + }); + + it('adds the request immediately', () => { + vm.loadRequestDetails('123', 'https://gitlab.com/'); + + expect(vm.store.addRequest).toHaveBeenCalledWith( + '123', + 'https://gitlab.com/', + ); + }); + + it('makes an HTTP request for the request details', () => { + spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough(); + + vm.loadRequestDetails('456', 'https://gitlab.com/'); + + expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith( + '/-/peek/results', + '456', + ); + }); + }); +}); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index a0ca8f8b4c3..bc00fdfd73c 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -6,6 +6,7 @@ import '~/commons'; import Vue from 'vue'; import VueResource from 'vue-resource'; import Translate from '~/vue_shared/translate'; +import jasmineDiff from 'jasmine-diff'; import { getDefaultAdapter } from '~/lib/utils/axios_utils'; import { FIXTURES_PATH, TEST_HOST } from './test_constants'; @@ -35,7 +36,15 @@ Vue.use(Translate); jasmine.getFixtures().fixturesPath = FIXTURES_PATH; jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH; -beforeAll(() => jasmine.addMatchers(customMatchers)); +beforeAll(() => { + jasmine.addMatchers( + jasmineDiff(jasmine, { + colors: true, + inline: true, + }), + ); + jasmine.addMatchers(customMatchers); +}); // globalize common libraries window.$ = $; diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js index 61b7bd2c226..8ac2f26979b 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js @@ -119,6 +119,7 @@ describe('MRWidgetHeader', () => { beforeEach(() => { vm = mountComponent(Component, { mr: { + iid: 1, divergedCommitsCount: 12, sourceBranch: 'mr-widget-refactor', sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', @@ -130,6 +131,8 @@ describe('MRWidgetHeader', () => { emailPatchesPath: '/mr/email-patches', plainDiffPath: '/mr/plainDiffPath', statusPath: 'abc', + sourceProjectFullPath: 'root/gitlab-ce', + targetProjectFullPath: 'gitlab-org/gitlab-ce', }, }); }); @@ -146,16 +149,40 @@ describe('MRWidgetHeader', () => { const button = vm.$el.querySelector('.js-web-ide'); expect(button.textContent.trim()).toEqual('Open in Web IDE'); - expect(button.getAttribute('href')).toEqual('/-/ide/projectabc'); + expect(button.getAttribute('href')).toEqual( + '/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=gitlab-org%2Fgitlab-ce', + ); }); - it('renders web ide button with relative URL', () => { + it('renders web ide button with blank query string if target & source project branch', done => { + vm.mr.targetProjectFullPath = 'root/gitlab-ce'; + + vm.$nextTick(() => { + const button = vm.$el.querySelector('.js-web-ide'); + + expect(button.textContent.trim()).toEqual('Open in Web IDE'); + expect(button.getAttribute('href')).toEqual( + '/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=', + ); + + done(); + }); + }); + + it('renders web ide button with relative URL', done => { gon.relative_url_root = '/gitlab'; + vm.mr.iid = 2; - const button = vm.$el.querySelector('.js-web-ide'); + vm.$nextTick(() => { + const button = vm.$el.querySelector('.js-web-ide'); - expect(button.textContent.trim()).toEqual('Open in Web IDE'); - expect(button.getAttribute('href')).toEqual('/-/ide/projectabc'); + expect(button.textContent.trim()).toEqual('Open in Web IDE'); + expect(button.getAttribute('href')).toEqual( + '/gitlab/-/ide/project/root/gitlab-ce/merge_requests/2?target_project=gitlab-org%2Fgitlab-ce', + ); + + done(); + }); }); it('renders download dropdown with links', () => { diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 9d2a15ff009..c0b5a7d4455 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -29,8 +29,10 @@ export default { source_branch: 'daaaa', source_branch_link: 'daaaa', source_project_id: 19, + source_project_full_path: '/group1/project1', target_branch: 'master', target_project_id: 19, + target_project_full_path: '/group2/project2', metrics: { merged_by: { name: 'Administrator', diff --git a/spec/javascripts/vue_shared/components/memory_graph_spec.js b/spec/javascripts/vue_shared/components/memory_graph_spec.js index 73a69df019e..65d8ed39ade 100644 --- a/spec/javascripts/vue_shared/components/memory_graph_spec.js +++ b/spec/javascripts/vue_shared/components/memory_graph_spec.js @@ -113,7 +113,7 @@ describe('MemoryGraph', () => { const circleEl = el.querySelector('circle'); expect(circleEl).toBeDefined(); expect(circleEl.getAttribute('r')).toBe('1.5'); - expect(circleEl.getAttribute('tranform')).toBe('translate(0 -1)'); + expect(circleEl.getAttribute('transform')).toBe('translate(0 -1)'); expect(circleEl.getAttribute('cx')).toBe(`${dotX}`); expect(circleEl.getAttribute('cy')).toBe(`${dotY}`); done(); diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 3a0688b7cbb..1f35d1e4880 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -379,6 +379,20 @@ describe Gitlab::ClosingIssueExtractor do .to match_array([issue, other_issue, third_issue]) end + it 'allows non-comma-separated issue numbers in single line message' do + message = "Closes #{reference} #{reference2} #{reference3}" + + expect(subject.closed_by_message(message)) + .to match_array([issue, other_issue, third_issue]) + end + + it 'allows mixed comma-separated and non-comma-separated issue numbers in single line message' do + message = "Closes #{reference}, #{reference2} and #{reference3}" + + expect(subject.closed_by_message(message)) + .to match_array([issue, other_issue, third_issue]) + end + it 'fetches issues in multi-line message' do message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}" diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 7c3d2af819b..11ab376ab8f 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -269,7 +269,7 @@ EOT before do # TODO use a Gitaly diff object instead rugged_commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + repository.rugged.rev_parse('5937ac0a7beb003549fc5fd26fc247adbce4a52e') end @diffs = rugged_commit.parents[0].diff(rugged_commit).patches diff --git a/spec/lib/gitlab/git/index_spec.rb b/spec/lib/gitlab/git/index_spec.rb index 16e6bd35449..e51b875be11 100644 --- a/spec/lib/gitlab/git/index_spec.rb +++ b/spec/lib/gitlab/git/index_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Git::Index, seed_helper: true do let(:index) { described_class.new(repository) } before do - index.read_tree(repository.lookup('master').tree) + index.read_tree(lookup('master').tree) end around do |example| @@ -30,7 +30,7 @@ describe Gitlab::Git::Index, seed_helper: true do entry = index.get(options[:file_path]) expect(entry).not_to be_nil - expect(repository.lookup(entry[:oid]).content).to eq(options[:content]) + expect(lookup(entry[:oid]).content).to eq(options[:content]) end end @@ -54,7 +54,7 @@ describe Gitlab::Git::Index, seed_helper: true do index.create(options) entry = index.get(options[:file_path]) - expect(repository.lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content])) + expect(lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content])) end end @@ -68,7 +68,7 @@ describe Gitlab::Git::Index, seed_helper: true do index.create(options) entry = index.get(options[:file_path]) - expect(repository.lookup(entry[:oid]).content).to eq("Hello,\nWorld") + expect(lookup(entry[:oid]).content).to eq("Hello,\nWorld") end end end @@ -135,7 +135,7 @@ describe Gitlab::Git::Index, seed_helper: true do entry = index.get(options[:file_path]) - expect(repository.lookup(entry[:oid]).content).to eq(options[:content]) + expect(lookup(entry[:oid]).content).to eq(options[:content]) end it 'preserves file mode' do @@ -190,7 +190,7 @@ describe Gitlab::Git::Index, seed_helper: true do entry = index.get(options[:file_path]) expect(entry).not_to be_nil - expect(repository.lookup(entry[:oid]).content).to eq(options[:content]) + expect(lookup(entry[:oid]).content).to eq(options[:content]) end it 'preserves file mode' do @@ -232,4 +232,8 @@ describe Gitlab::Git::Index, seed_helper: true do end end end + + def lookup(revision) + repository.rugged.rev_parse(revision) + end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 64b08dd9c4b..6480f6c407d 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -321,90 +321,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - context '#submodules' do - around do |example| - # TODO #submodules will be removed, has been migrated to gitaly - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - example.run - end - end - - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } - - context 'where repo has submodules' do - let(:submodules) { repository.send(:submodules, 'master') } - let(:submodule) { submodules.first } - - it { expect(submodules).to be_kind_of Hash } - it { expect(submodules.empty?).to be_falsey } - - it 'should have valid data' do - expect(submodule).to eq([ - "six", { - "id" => "409f37c4f05865e4fb208c771485f211a22c4c2d", - "name" => "six", - "url" => "git://github.com/randx/six.git" - } - ]) - end - - it 'should handle nested submodules correctly' do - nested = submodules['nested/six'] - expect(nested['name']).to eq('nested/six') - expect(nested['url']).to eq('git://github.com/randx/six.git') - expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196') - end - - it 'should handle deeply nested submodules correctly' do - nested = submodules['deeper/nested/six'] - expect(nested['name']).to eq('deeper/nested/six') - expect(nested['url']).to eq('git://github.com/randx/six.git') - expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196') - end - - it 'should not have an entry for an invalid submodule' do - expect(submodules).not_to have_key('invalid/path') - end - - it 'should not have an entry for an uncommited submodule dir' do - submodules = repository.send(:submodules, 'fix-existing-submodule-dir') - expect(submodules).not_to have_key('submodule-existing-dir') - end - - it 'should handle tags correctly' do - submodules = repository.send(:submodules, 'v1.2.1') - - expect(submodules.first).to eq([ - "six", { - "id" => "409f37c4f05865e4fb208c771485f211a22c4c2d", - "name" => "six", - "url" => "git://github.com/randx/six.git" - } - ]) - end - - it 'should not break on invalid syntax' do - allow(repository).to receive(:blob_content).and_return(<<-GITMODULES.strip_heredoc) - [submodule "six"] - path = six - url = git://github.com/randx/six.git - - [submodule] - foo = bar - GITMODULES - - expect(submodules).to have_key('six') - end - end - - context 'where repo doesn\'t have submodules' do - let(:submodules) { repository.send(:submodules, '6d39438') } - it 'should return an empty hash' do - expect(submodules).to be_empty - end - end - end - describe '#commit_count' do shared_examples 'simple commit counting' do it { expect(repository.commit_count("master")).to eq(25) } diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index 2223f163177..90e6d653d34 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -9,6 +9,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) end after do diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb new file mode 100644 index 00000000000..9c3870a0af8 --- /dev/null +++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +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(:exported_file_path) { "#{shared.export_path}/uploads/#{upload.secret}/#{File.basename(upload.path)}" } + + subject(:manager) { described_class.new(project: project, shared: shared) } + + before do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + FileUtils.mkdir_p(shared.export_path) + end + + after do + FileUtils.rm_rf(shared.export_path) + end + + describe '#save' do + context 'when the project has uploads locally stored' do + let(:upload) { create(:upload, :issuable_upload, :with_file, model: project) } + + before do + project.uploads << upload + end + + it 'does not cause errors' do + manager.save + + expect(shared.errors).to be_empty + end + + it 'copies the file in the correct location when there is an upload' do + manager.save + + expect(File).to exist(exported_file_path) + 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 + + expect(File.read(exported_file_path)).to eq('File content') + end + 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 + + expect(project.uploads.size).to eq(1) + expect(project.uploads.first.build_uploader.filename).to eq('dummy.txt') + end + end + 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 095687fa89d..c716edd9397 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -7,6 +7,7 @@ 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 @@ -30,7 +31,7 @@ describe Gitlab::ImportExport::UploadsSaver do it 'copies the uploads to the export path' do saver.save - uploads = Dir.glob(File.join(saver.uploads_export_path, '**/*')).map { |file| File.basename(file) } + uploads = Dir.glob(File.join(shared.export_path, 'uploads/**/*')).map { |file| File.basename(file) } expect(uploads).to include('banana_sample.gif') end @@ -52,7 +53,7 @@ describe Gitlab::ImportExport::UploadsSaver do it 'copies the uploads to the export path' do saver.save - uploads = Dir.glob(File.join(saver.uploads_export_path, '**/*')).map { |file| File.basename(file) } + uploads = Dir.glob(File.join(shared.export_path, 'uploads/**/*')).map { |file| File.basename(file) } expect(uploads).to include('banana_sample.gif') end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index e01906f4b6c..b335e0fbeb3 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -157,22 +157,4 @@ describe Deployment do end end end - - describe '#stop_action?' do - subject { deployment.stop_action? } - - context 'when no other actions' do - let(:deployment) { build(:deployment) } - - it { is_expected.to be_falsey } - end - - context 'when matching action is defined' do - let(:build) { create(:ci_build) } - let(:deployment) { FactoryBot.build(:deployment, deployable: build, on_stop: 'close_app') } - let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } - - it { is_expected.to be_truthy } - end - end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 4bded9efe91..c65e0b81451 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -170,8 +170,8 @@ describe Environment do end end - describe '#stop_action?' do - subject { environment.stop_action? } + describe '#stop_action_available?' do + subject { environment.stop_action_available? } context 'when no other actions' do it { is_expected.to be_falsey } @@ -179,8 +179,17 @@ describe Environment do context 'when matching action is defined' do let(:build) { create(:ci_build) } - let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } - let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } + + let!(:deployment) do + create(:deployment, environment: environment, + deployable: build, + on_stop: 'close_app') + end + + let!(:close_action) do + create(:ci_build, :manual, pipeline: build.pipeline, + name: 'close_app') + end context 'when environment is available' do before do diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 113703fac38..246947e58a8 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -514,6 +514,38 @@ describe API::Commits do expect(response).to have_gitlab_http_status(400) end end + + context 'when committing into a fork as a maintainer' do + include_context 'merge request allowing collaboration' + + let(:project_id) { forked_project.id } + + def push_params(branch_name) + { + branch: branch_name, + commit_message: 'Hello world', + actions: [ + { + action: 'create', + file_path: 'foo/bar/baz.txt', + content: 'puts 8' + } + ] + } + end + + it 'allows pushing to the source branch of the merge request' do + post api(url, user), push_params('feature') + + expect(response).to have_gitlab_http_status(:created) + end + + it 'denies pushing to another branch' do + post api(url, user), push_params('other-branch') + + expect(response).to have_gitlab_http_status(:forbidden) + end + end end describe 'GET /projects/:id/repository/commits/:sha/refs' do @@ -1065,11 +1097,29 @@ describe API::Commits do it 'returns 400 if you are not allowed to push to the target branch' do post api(route, current_user), branch: 'feature' - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('You are not allowed to push into this branch') + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to match(/You are not allowed to push into this branch/) end end end + + context 'when cherry picking to a fork as a maintainer' do + include_context 'merge request allowing collaboration' + + let(:project_id) { forked_project.id } + + it 'allows access from a maintainer that to the source branch' do + post api(route, user), branch: 'feature' + + expect(response).to have_gitlab_http_status(:created) + end + + it 'denies cherry picking to another branch' do + post api(route, user), branch: 'master' + + expect(response).to have_gitlab_http_status(:forbidden) + end + end end describe 'POST /projects/:id/repository/commits/:sha/comments' do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 8389cb7cf9c..f72c01561d8 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -237,6 +237,39 @@ describe API::Projects do end end + context 'and using archived' do + let!(:archived_project) { create(:project, creator_id: user.id, namespace: user.namespace, archived: true) } + + it 'returns archived projects' do + get api('/projects?archived=true', 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.length).to eq(Project.public_or_visible_to_user(user).where(archived: true).size) + expect(json_response.map { |project| project['id'] }).to include(archived_project.id) + end + + it 'returns non-archived projects' do + get api('/projects?archived=false', 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.length).to eq(Project.public_or_visible_to_user(user).where(archived: false).size) + expect(json_response.map { |project| project['id'] }).not_to include(archived_project.id) + end + + it 'returns every project' do + get api('/projects', 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.map { |project| project['id'] }).to contain_exactly(*Project.public_or_visible_to_user(user).pluck(:id)) + end + end + context 'and using search' do it_behaves_like 'projects response' do let(:filter) { { search: project.name } } diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index d2072198d83..0ba2539a717 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -11,6 +11,21 @@ describe MergeRequestWidgetEntity do described_class.new(resource, request: request).as_json end + describe 'source_project_full_path' do + it 'includes the full path of the source project' do + expect(subject[:source_project_full_path]).to be_present + end + + context 'when the source project is missing' do + it 'returns `nil` for the source project' do + resource.allow_broken = true + resource.update!(source_project: nil) + + expect(subject[:source_project_full_path]).to be_nil + end + end + end + describe 'pipeline' do let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.source_branch, sha: resource.source_branch_sha, head_pipeline_of: resource) } diff --git a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb new file mode 100644 index 00000000000..05424d08b9d --- /dev/null +++ b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb @@ -0,0 +1,15 @@ +shared_context 'merge request allowing collaboration' do + include ProjectForksHelper + + let(:canonical) { create(:project, :public, :repository) } + let(:forked_project) { fork_project(canonical, nil, repository: true) } + + before do + canonical.add_maintainer(user) + create(:merge_request, + target_project: canonical, + source_project: forked_project, + source_branch: 'feature', + allow_collaboration: true) + end +end diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index 7ba28b4fc1f..3efe512a59c 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -124,6 +124,15 @@ describe FileUploader do end end + describe '.extract_dynamic_path' do + it 'works with hashed storage' do + path = 'export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/72a497a02fe3ee09edae2ed06d390038/dummy.txt' + + expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt') + expect(described_class.extract_dynamic_path(path)[:secret]).to eq('72a497a02fe3ee09edae2ed06d390038') + end + end + describe '#secret' do it 'generates a secret if none is provided' do expect(described_class).to receive(:generate_secret).and_return('secret') |