diff options
Diffstat (limited to 'spec')
28 files changed, 2847 insertions, 2130 deletions
diff --git a/spec/features/issues/user_creates_confidential_merge_request_spec.rb b/spec/features/issues/user_creates_confidential_merge_request_spec.rb new file mode 100644 index 00000000000..7ae4af4667b --- /dev/null +++ b/spec/features/issues/user_creates_confidential_merge_request_spec.rb @@ -0,0 +1,54 @@ +require 'rails_helper' + +describe 'User creates confidential merge request on issue page', :js do + include ProjectForksHelper + + let(:user) { create(:user) } + let(:project) { create(:project, :repository, :public) } + let(:issue) { create(:issue, project: project, confidential: true) } + + def visit_confidential_issue + sign_in(user) + visit project_issue_path(project, issue) + wait_for_requests + end + + before do + project.add_developer(user) + end + + context 'user has no private fork' do + before do + fork_project(project, user, repository: true) + visit_confidential_issue + end + + it 'shows that user has no fork available' do + click_button 'Create confidential merge request' + + page.within '.create-confidential-merge-request-dropdown-menu' do + expect(page).to have_content('No forks available to you') + end + end + end + + describe 'user has private fork' do + let(:forked_project) { fork_project(project, user, repository: true) } + + before do + forked_project.update(visibility: Gitlab::VisibilityLevel::PRIVATE) + visit_confidential_issue + end + + it 'create merge request in fork' do + click_button 'Create confidential merge request' + + page.within '.create-confidential-merge-request-dropdown-menu' do + expect(page).to have_button(forked_project.name_with_namespace) + click_button 'Create confidential merge request' + end + + expect(page).to have_content(forked_project.namespace.name) + end + end +end diff --git a/spec/finders/runner_jobs_finder_spec.rb b/spec/finders/runner_jobs_finder_spec.rb index 97304170c4e..01f45a37ba8 100644 --- a/spec/finders/runner_jobs_finder_spec.rb +++ b/spec/finders/runner_jobs_finder_spec.rb @@ -35,5 +35,27 @@ describe RunnerJobsFinder do end end end + + context 'when order_by and sort are specified' do + context 'when order_by id and sort is asc' do + let(:params) { { order_by: 'id', sort: 'asc' } } + let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) } + + it 'sorts as id: :asc' do + is_expected.to eq(jobs.sort_by(&:id)) + end + end + end + + context 'when order_by is specified and sort is not specified' do + context 'when order_by id and sort is not specified' do + let(:params) { { order_by: 'id' } } + let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) } + + it 'sorts as id: :desc' do + is_expected.to eq(jobs.sort_by(&:id).reverse) + end + end + end end end diff --git a/spec/frontend/branches/divergence_graph_spec.js b/spec/frontend/branches/divergence_graph_spec.js index 4ed77c3a036..8283bc966e4 100644 --- a/spec/frontend/branches/divergence_graph_spec.js +++ b/spec/frontend/branches/divergence_graph_spec.js @@ -10,12 +10,14 @@ describe('Divergence graph', () => { mock.onGet('/-/diverging_counts').reply(200, { master: { ahead: 1, behind: 1 }, + 'test/hello-world': { ahead: 1, behind: 1 }, }); jest.spyOn(axios, 'get'); document.body.innerHTML = ` - <div class="js-branch-item" data-name="master"></div> + <div class="js-branch-item" data-name="master"><div class="js-branch-divergence-graph"></div></div> + <div class="js-branch-item" data-name="test/hello-world"><div class="js-branch-divergence-graph"></div></div> `; }); @@ -26,7 +28,13 @@ describe('Divergence graph', () => { it('calls axos get with list of branch names', () => init('/-/diverging_counts').then(() => { expect(axios.get).toHaveBeenCalledWith('/-/diverging_counts', { - params: { names: ['master'] }, + params: { names: ['master', 'test/hello-world'] }, }); })); + + it('creates Vue components', () => + init('/-/diverging_counts').then(() => { + expect(document.querySelector('[data-name="master"]').innerHTML).not.toEqual(''); + expect(document.querySelector('[data-name="test/hello-world"]').innerHTML).not.toEqual(''); + })); }); diff --git a/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap new file mode 100644 index 00000000000..a241c764df7 --- /dev/null +++ b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Confidential merge request project form group component renders empty state when response is empty 1`] = ` +<div + class="form-group" +> + <label> + Project + </label> + + <div> + <!----> + + <p + class="text-muted mt-1 mb-0" + > + + No forks available to you. + <br /> + + <span> + To protect this issues confidentiality, + <a + class="help-link" + href="https://test.com" + > + fork the project + </a> + and set the forks visiblity to private. + </span> + + <gllink-stub + class="help-link" + href="/help" + target="_blank" + > + <span + class="sr-only" + > + Read more + </span> + + <i + aria-hidden="true" + class="fa fa-question-circle" + /> + </gllink-stub> + </p> + </div> +</div> +`; + +exports[`Confidential merge request project form group component renders fork dropdown 1`] = ` +<div + class="form-group" +> + <label> + Project + </label> + + <div> + <!----> + + <p + class="text-muted mt-1 mb-0" + > + + No forks available to you. + <br /> + + <span> + To protect this issues confidentiality, + <a + class="help-link" + href="https://test.com" + > + fork the project + </a> + and set the forks visiblity to private. + </span> + + <gllink-stub + class="help-link" + href="/help" + target="_blank" + > + <span + class="sr-only" + > + Read more + </span> + + <i + aria-hidden="true" + class="fa fa-question-circle" + /> + </gllink-stub> + </p> + </div> +</div> +`; diff --git a/spec/frontend/confidential_merge_request/components/dropdown_spec.js b/spec/frontend/confidential_merge_request/components/dropdown_spec.js new file mode 100644 index 00000000000..69495f3c161 --- /dev/null +++ b/spec/frontend/confidential_merge_request/components/dropdown_spec.js @@ -0,0 +1,56 @@ +import { mount } from '@vue/test-utils'; +import { GlDropdownItem } from '@gitlab/ui'; +import Dropdown from '~/confidential_merge_request/components/dropdown.vue'; + +let vm; + +function factory(projects = []) { + vm = mount(Dropdown, { + propsData: { + projects, + selectedProject: projects[0], + }, + }); +} + +describe('Confidential merge request project dropdown component', () => { + afterEach(() => { + vm.destroy(); + }); + + it('renders dropdown items', () => { + factory([ + { + id: 1, + name: 'test', + }, + { + id: 2, + name: 'test', + }, + ]); + + expect(vm.findAll(GlDropdownItem).length).toBe(2); + }); + + it('renders selected project icon', () => { + factory([ + { + id: 1, + name: 'test', + }, + { + id: 2, + name: 'test 2', + }, + ]); + + expect(vm.find('.js-active-project-check').classes()).not.toContain('icon'); + expect( + vm + .findAll('.js-active-project-check') + .at(1) + .classes(), + ).toContain('icon'); + }); +}); diff --git a/spec/frontend/confidential_merge_request/components/project_form_group_spec.js b/spec/frontend/confidential_merge_request/components/project_form_group_spec.js new file mode 100644 index 00000000000..3001363f7b9 --- /dev/null +++ b/spec/frontend/confidential_merge_request/components/project_form_group_spec.js @@ -0,0 +1,77 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import ProjectFormGroup from '~/confidential_merge_request/components/project_form_group.vue'; + +const localVue = createLocalVue(); +const mockData = [ + { + id: 1, + name_with_namespace: 'root / gitlab-ce', + path_with_namespace: 'root/gitlab-ce', + namespace: { + full_path: 'root', + }, + }, + { + id: 2, + name_with_namespace: 'test / gitlab-ce', + path_with_namespace: 'test/gitlab-ce', + namespace: { + full_path: 'test', + }, + }, +]; +let vm; +let mock; + +function factory(projects = mockData) { + mock = new MockAdapter(axios); + mock.onGet(/api\/(.*)\/projects\/gitlab-org%2Fgitlab-ce\/forks/).reply(200, projects); + + vm = shallowMount(ProjectFormGroup, { + localVue, + propsData: { + namespacePath: 'gitlab-org', + projectPath: 'gitlab-org/gitlab-ce', + newForkPath: 'https://test.com', + helpPagePath: '/help', + }, + }); +} + +describe('Confidential merge request project form group component', () => { + afterEach(() => { + mock.restore(); + vm.destroy(); + }); + + it('renders fork dropdown', () => { + factory(); + + return localVue.nextTick(() => { + expect(vm.element).toMatchSnapshot(); + }); + }); + + it('sets selected project as first fork', () => { + factory(); + + return localVue.nextTick(() => { + expect(vm.vm.selectedProject).toEqual({ + id: 1, + name: 'root / gitlab-ce', + pathWithNamespace: 'root/gitlab-ce', + namespaceFullpath: 'root', + }); + }); + }); + + it('renders empty state when response is empty', () => { + factory([]); + + return localVue.nextTick(() => { + expect(vm.element).toMatchSnapshot(); + }); + }); +}); diff --git a/spec/javascripts/create_merge_request_dropdown_spec.js b/spec/frontend/create_merge_request_dropdown_spec.js index 00fe3f451f5..6e41fdabdce 100644 --- a/spec/javascripts/create_merge_request_dropdown_spec.js +++ b/spec/frontend/create_merge_request_dropdown_spec.js @@ -1,7 +1,7 @@ import axios from '~/lib/utils/axios_utils'; import MockAdapter from 'axios-mock-adapter'; import CreateMergeRequestDropdown from '~/create_merge_request_dropdown'; -import { TEST_HOST } from 'spec/test_constants'; +import { TEST_HOST } from './helpers/test_constants'; describe('CreateMergeRequestDropdown', () => { let axiosMock; @@ -10,7 +10,7 @@ describe('CreateMergeRequestDropdown', () => { beforeEach(() => { axiosMock = new MockAdapter(axios); - setFixtures(` + document.body.innerHTML = ` <div id="dummy-wrapper-element"> <div class="available"></div> <div class="unavailable"> @@ -18,11 +18,12 @@ describe('CreateMergeRequestDropdown', () => { <div class="text"></div> </div> <div class="js-ref"></div> + <div class="js-create-mr"></div> <div class="js-create-merge-request"></div> <div class="js-create-target"></div> <div class="js-dropdown-toggle"></div> </div> - `); + `; const dummyElement = document.getElementById('dummy-wrapper-element'); dropdown = new CreateMergeRequestDropdown(dummyElement); @@ -36,7 +37,7 @@ describe('CreateMergeRequestDropdown', () => { describe('getRef', () => { it('escapes branch names correctly', done => { const endpoint = `${dropdown.refsPath}contains%23hash`; - spyOn(axios, 'get').and.callThrough(); + jest.spyOn(axios, 'get'); axiosMock.onGet(endpoint).replyOnce({}); dropdown diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 65f72a135aa..c97ff5236ec 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import $ from 'jquery'; import _ from 'underscore'; +import Api from '~/api'; import { TEST_HOST } from 'spec/test_constants'; import { headersInterceptor } from 'spec/helpers/vue_resource_helper'; import actionsModule, * as actions from '~/notes/stores/actions'; @@ -8,7 +9,6 @@ import * as mutationTypes from '~/notes/stores/mutation_types'; import * as notesConstants from '~/notes/constants'; import createStore from '~/notes/stores'; import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub'; -import service from '~/notes/services/notes_service'; import testAction from '../../helpers/vuex_action_helper'; import { resetStore } from '../helpers'; import { @@ -846,9 +846,9 @@ describe('Actions Notes Store', () => { let flashContainer; beforeEach(() => { - spyOn(service, 'applySuggestion'); + spyOn(Api, 'applySuggestion'); dispatch.and.returnValue(Promise.resolve()); - service.applySuggestion.and.returnValue(Promise.resolve()); + Api.applySuggestion.and.returnValue(Promise.resolve()); flashContainer = {}; }); @@ -877,7 +877,7 @@ describe('Actions Notes Store', () => { it('when service fails, flashes error message', done => { const response = { response: { data: { message: TEST_ERROR_MESSAGE } } }; - service.applySuggestion.and.returnValue(Promise.reject(response)); + Api.applySuggestion.and.returnValue(Promise.reject(response)); testSubmitSuggestion(done, () => { expect(commit).not.toHaveBeenCalled(); diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index aea02d21048..b755cd1aff0 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -610,4 +610,17 @@ describe Gitlab::Diff::Position do it_behaves_like "diff position json" end end + + describe "#file_hash" do + subject do + described_class.new( + old_path: "image.jpg", + new_path: "image.jpg" + ) + end + + it "returns SHA1 representation of the file_path" do + expect(subject.file_hash).to eq(Digest::SHA1.hexdigest(subject.file_path)) + end + end end diff --git a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb new file mode 100644 index 00000000000..900816af53a --- /dev/null +++ b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb @@ -0,0 +1,238 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Diff::PositionTracer::ImageStrategy do + include PositionTracerHelpers + + let(:project) { create(:project, :repository) } + let(:current_user) { project.owner } + let(:file_name) { 'test-file' } + let(:new_file_name) { "#{file_name}-new" } + let(:second_file_name) { "#{file_name}-2" } + let(:branch_name) { 'position-tracer-test' } + let(:old_position) { position(old_path: file_name, new_path: file_name, position_type: 'image') } + + let(:tracer) do + Gitlab::Diff::PositionTracer.new( + project: project, + old_diff_refs: old_diff_refs, + new_diff_refs: new_diff_refs + ) + end + + let(:strategy) { described_class.new(tracer) } + + subject { strategy.trace(old_position) } + + let(:initial_commit) do + project.commit(create_branch(branch_name, 'master')[:branch].name) + end + + describe '#trace' do + describe 'diff scenarios' do + let(:create_file_commit) do + initial_commit + + create_file( + branch_name, + file_name, + Base64.encode64('content') + ) + end + + let(:update_file_commit) do + create_file_commit + + update_file( + branch_name, + file_name, + Base64.encode64('updatedcontent') + ) + end + + let(:update_file_again_commit) do + update_file_commit + + update_file( + branch_name, + file_name, + Base64.encode64('updatedcontentagain') + ) + end + + let(:delete_file_commit) do + create_file_commit + delete_file(branch_name, file_name) + end + + let(:rename_file_commit) do + delete_file_commit + + create_file( + branch_name, + new_file_name, + Base64.encode64('renamedcontent') + ) + end + + let(:create_second_file_commit) do + create_file_commit + + create_file( + branch_name, + second_file_name, + Base64.encode64('morecontent') + ) + end + + let(:create_another_file_commit) do + create_file( + branch_name, + second_file_name, + Base64.encode64('morecontent') + ) + end + + let(:update_another_file_commit) do + update_file( + branch_name, + second_file_name, + Base64.encode64('updatedmorecontent') + ) + end + + context 'when the file was created in the old diff' do + context 'when the file is unchanged between the old and the new diff' do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) } + + it 'returns the new position' do + expect_new_position( + old_path: file_name, + new_path: file_name + ) + end + end + + context 'when the file was updated between the old and the new diff' do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_file_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + + it 'returns the position of the change' do + expect_change_position( + old_path: file_name, + new_path: file_name + ) + end + end + + context 'when the file was renamed in between the old and the new diff' do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, rename_file_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, rename_file_commit) } + + it 'returns the position of the change' do + expect_change_position( + old_path: file_name, + new_path: file_name + ) + end + end + + context 'when the file was removed in between the old and the new diff' do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, delete_file_commit) } + + it 'returns the position of the change' do + expect_change_position( + old_path: file_name, + new_path: file_name + ) + end + end + + context 'when the file is unchanged in the new diff' do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(create_another_file_commit, update_another_file_commit) } + let(:change_diff_refs) { diff_refs(initial_commit, create_another_file_commit) } + + it 'returns the position of the change' do + expect_change_position( + old_path: file_name, + new_path: file_name + ) + end + end + end + + context 'when the file was changed in the old diff' do + context 'when the file is unchanged in between the old and the new diff' do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) } + + it 'returns the new position' do + expect_new_position( + old_path: file_name, + new_path: file_name + ) + end + end + + context 'when the file was updated in between the old and the new diff' do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:change_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) } + + it 'returns the position of the change' do + expect_change_position( + old_path: file_name, + new_path: file_name + ) + end + end + + context 'when the file was renamed in between the old and the new diff' do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, rename_file_commit) } + let(:change_diff_refs) { diff_refs(update_file_commit, rename_file_commit) } + + it 'returns the position of the change' do + expect_change_position( + old_path: file_name, + new_path: file_name + ) + end + end + + context 'when the file was removed in between the old and the new diff' do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(update_file_commit, delete_file_commit) } + + it 'returns the position of the change' do + expect_change_position( + old_path: file_name, + new_path: file_name + ) + end + end + + context 'when the file is unchanged in the new diff' do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_another_file_commit, update_another_file_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, create_another_file_commit) } + + it 'returns the position of the change' do + expect_change_position( + old_path: file_name, + new_path: file_name + ) + end + end + end + end + end +end diff --git a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb new file mode 100644 index 00000000000..7f4902c5b86 --- /dev/null +++ b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb @@ -0,0 +1,1805 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Diff::PositionTracer::LineStrategy do + # Douwe's diary New York City, 2016-06-28 + # -------------------------------------------------------------------------- + # + # Dear diary, + # + # Ideally, we would have a test for every single diff scenario that can + # occur and that the PositionTracer should correctly trace a position + # through, across the following variables: + # + # - Old diff file type: created, changed, renamed, deleted, unchanged (5) + # - Old diff line type: added, removed, unchanged (3) + # - New diff file type: created, changed, renamed, deleted, unchanged (5) + # - New diff line type: added, removed, unchanged (3) + # - Old-to-new diff line change: kept, moved, undone (3) + # + # This adds up to 5 * 3 * 5 * 3 * 3 = 675 different potential scenarios, + # and 675 different tests to cover them all. In reality, it would be fewer, + # since one cannot have a removed line in a created file diff, for example, + # but for the sake of this diary entry, let's be pessimistic. + # + # Writing these tests is a manual and time consuming process, as every test + # requires the manual construction or finding of a combination of diffs that + # create the exact diff scenario we are looking for, and can take between + # 1 and 10 minutes, depending on the farfetchedness of the scenario and + # complexity of creating it. + # + # This means that writing tests to cover all of these scenarios would end up + # taking between 11 and 112 hours in total, which I do not believe is the + # best use of my time. + # + # A better course of action would be to think of scenarios that are likely + # to occur, but also potentially tricky to trace correctly, and only cover + # those, with a few more obvious scenarios thrown in to cover our bases. + # + # Unfortunately, I only came to the above realization once I was about + # 1/5th of the way through the process of writing ALL THE SPECS, having + # already wasted about 3 hours trying to be thorough. + # + # I did find 2 bugs while writing those though, so that's good. + # + # In any case, all of this means that the tests below will be extremely + # (excessively, unjustifiably) thorough for scenarios where "the file was + # created in the old diff" and then drop off to comparatively lackluster + # testing of other scenarios. + # + # I did still try to cover most of the obvious and potentially tricky + # scenarios, though. + + include RepoHelpers + include PositionTracerHelpers + + let(:project) { create(:project, :repository) } + let(:current_user) { project.owner } + let(:repository) { project.repository } + let(:file_name) { "test-file" } + let(:new_file_name) { "#{file_name}-new" } + let(:second_file_name) { "#{file_name}-2" } + let(:branch_name) { "position-tracer-test" } + + let(:old_diff_refs) { raise NotImplementedError } + let(:new_diff_refs) { raise NotImplementedError } + let(:change_diff_refs) { raise NotImplementedError } + let(:old_position) { raise NotImplementedError } + + let(:tracer) do + Gitlab::Diff::PositionTracer.new( + project: project, + old_diff_refs: old_diff_refs, + new_diff_refs: new_diff_refs + ) + end + + let(:strategy) { described_class.new(tracer) } + + subject { strategy.trace(old_position) } + + let(:initial_commit) do + project.commit(create_branch(branch_name, 'master')[:branch].name) + end + + describe "#trace" do + describe "diff scenarios" do + let(:create_file_commit) do + initial_commit + + create_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + B + C + CONTENT + ) + end + + let(:create_second_file_commit) do + create_file_commit + + create_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + E + CONTENT + ) + end + + let(:update_line_commit) do + create_second_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + CONTENT + ) + end + + let(:update_second_file_line_commit) do + update_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + EE + CONTENT + ) + end + + let(:move_line_commit) do + update_second_file_line_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + BB + A + C + CONTENT + ) + end + + let(:add_second_file_line_commit) do + move_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + EE + F + CONTENT + ) + end + + let(:move_second_file_line_commit) do + add_second_file_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + F + EE + CONTENT + ) + end + + let(:delete_line_commit) do + move_second_file_line_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + BB + A + CONTENT + ) + end + + let(:delete_second_file_line_commit) do + delete_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + F + CONTENT + ) + end + + let(:delete_file_commit) do + delete_second_file_line_commit + + delete_file(branch_name, file_name) + end + + let(:rename_file_commit) do + delete_file_commit + + create_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + BB + A + CONTENT + ) + end + + let(:update_line_again_commit) do + rename_file_commit + + update_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + BB + AA + CONTENT + ) + end + + let(:move_line_again_commit) do + update_line_again_commit + + update_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + AA + BB + CONTENT + ) + end + + let(:delete_line_again_commit) do + move_line_again_commit + + update_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + AA + CONTENT + ) + end + + context "when the file was created in the old diff" do + context "when the file is created in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 + A + # 2 + B + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 + BB + # 2 + A + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: 1 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:change_diff_refs) { diff_refs(update_line_commit, delete_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 + A + # 2 + BB + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) + end + end + end + end + end + + context "when the file is changed in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 - A + # 2 1 BB + # 2 + A + # 3 3 C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 - A + # 2 1 BB + # 2 + A + # 3 3 C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: 1 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 1 BB + # 2 2 A + # 3 - C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) + end + end + end + end + end + + context "when the file is renamed in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, rename_file_commit) } + let(:change_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 1 BB + # 2 2 A + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: nil, + new_line: 2 + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 1 BB + # 2 - A + # 2 + AA + + it "returns the new position" do + expect_new_position( + old_path: file_name, + new_path: new_file_name, + old_line: old_position.new_line, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, move_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 + AA + # 1 2 BB + # 2 - A + + it "returns the new position" do + expect_new_position( + old_path: file_name, + new_path: new_file_name, + old_line: 1, + new_line: 2 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } + let(:change_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 1 BB + # 2 - A + # 2 + AA + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: new_file_name, + old_line: 2, + new_line: nil + ) + end + end + end + end + end + + context "when the file is deleted in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # 1 - BB + # 2 - A + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 - BB + # 2 - A + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(update_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 - BB + # 2 - A + # 3 - C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 - A + # 2 - BB + # 3 - C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 - BB + # 2 - A + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) + end + end + end + end + end + + context "when the file is unchanged in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) } + let(:change_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 2 B + # 3 3 C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: nil, + new_line: 2 + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } + let(:change_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 2 BB + # 3 3 C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: nil, + new_line: 1 + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, move_second_file_line_commit) } + let(:change_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 1 BB + # 2 2 A + # 3 3 C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: nil, + new_line: 1 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 2 BB + # 3 3 C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_second_file_line_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 1 BB + # 2 2 A + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) + end + end + end + end + end + end + + context "when the file was changed in the old diff" do + context "when the file is created in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + BB + # 2 + A + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + BB + # 2 + A + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: 1 + ) + end + end + + context "when that line was changed or deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, create_file_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + A + # 2 + B + # 3 + C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 1, + new_line: nil + ) + end + end + end + end + + context "when the position pointed at a deleted line in the old diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, initial_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) + end + end + + context "when the position pointed at an unchanged line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 1) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 2) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + BB + # 2 + A + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2, new_line: 2) } + + # old diff: + # 1 1 BB + # 2 2 A + # 3 - C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: 1 + ) + end + end + + context "when that line was changed or deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 3, new_line: 3) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + A + # 2 + B + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) + end + end + end + end + end + + context "when the file is changed in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 1 BB + # 2 2 A + # 3 - C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: 1, + new_line: 1 + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 - A + # 2 1 BB + # 2 + A + # 3 3 C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: 2, + new_line: 1 + ) + end + end + + context "when that line was changed or deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 1, + new_line: nil + ) + end + end + end + end + + context "when the position pointed at a deleted line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: old_position.old_line, + new_line: nil + ) + end + end + end + end + end + end + + describe "typical use scenarios" do + let(:second_branch_name) { "#{branch_name}-2" } + + def expect_new_positions(old_attrs, new_attrs) + old_positions = old_attrs.map do |old_attrs| + position(old_attrs) + end + + new_positions = old_positions.map do |old_position| + strategy.trace(old_position) + end + + aggregate_failures do + new_positions.zip(new_attrs).each do |new_position, new_attrs| + if new_attrs&.delete(:change) + expect_change_position(new_attrs, new_position) + else + expect_new_position(new_attrs, new_position) + end + end + end + end + + let(:create_file_commit) do + initial_commit + + create_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + B + C + D + E + F + CONTENT + ) + end + + let(:second_create_file_commit) do + create_file_commit + + create_branch(second_branch_name, branch_name) + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + Z + Z + Z + A + B + C + D + E + F + CONTENT + ) + end + + let(:update_file_commit) do + second_create_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + C + DD + E + F + G + CONTENT + ) + end + + let(:update_file_again_commit) do + update_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + D + E + FF + G + CONTENT + ) + end + + describe "simple push of new commit" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:change_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 3 2 C + # 4 - D + # 3 + DD + # 5 4 E + # 6 5 F + # 6 + G + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C + { old_path: file_name, old_line: 4 }, # - D + { new_path: file_name, new_line: 3 }, # + DD + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E + { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F + { new_path: file_name, new_line: 6 } # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2 }, + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, + { new_path: file_name, new_line: 4, change: true }, + { new_path: file_name, old_line: 3, change: true }, + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, + { new_path: file_name, old_line: 5, change: true }, + { new_path: file_name, new_line: 7 } + ] + + expect_new_positions(old_position_attrs, new_position_attrs) + end + end + + describe "force push to overwrite last commit" do + let(:second_create_file_commit) do + create_file_commit + + create_branch(second_branch_name, branch_name) + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + D + E + FF + G + CONTENT + ) + end + + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, second_create_file_commit) } + let(:change_diff_refs) { diff_refs(update_file_commit, second_create_file_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 3 2 C + # 4 - D + # 3 + DD + # 5 4 E + # 6 5 F + # 6 + G + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C + { old_path: file_name, old_line: 4 }, # - D + { new_path: file_name, new_line: 3 }, # + DD + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E + { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F + { new_path: file_name, new_line: 6 } # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2 }, + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, + { new_path: file_name, new_line: 4, change: true }, + { old_path: file_name, old_line: 3, change: true }, + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, + { old_path: file_name, old_line: 5, change: true }, + { new_path: file_name, new_line: 7 } + ] + + expect_new_positions(old_position_attrs, new_position_attrs) + end + end + + describe "force push to delete last commit" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:change_diff_refs) { diff_refs(update_file_again_commit, update_file_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 1 A + # 2 - B + # 3 2 C + # 4 - D + # 3 + DD + # 5 4 E + # 6 5 F + # 6 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 } # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2 }, + { old_path: file_name, old_line: 2, change: true }, + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, + { old_path: file_name, old_line: 4, change: true }, + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, + { new_path: file_name, new_line: 5, change: true }, + { old_path: file_name, old_line: 6, change: true }, + { new_path: file_name, new_line: 6 } + ] + + expect_new_positions(old_position_attrs, new_position_attrs) + end + end + + describe "rebase on top of target branch" do + let(:second_update_file_commit) do + update_file_commit + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + Z + Z + Z + A + C + DD + E + F + G + CONTENT + ) + end + + let(:update_file_again_commit) do + second_update_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + D + E + FF + G + CONTENT + ) + end + + let(:overwrite_update_file_again_commit) do + update_file_again_commit + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + Z + Z + Z + A + BB + C + D + E + FF + G + CONTENT + ) + end + + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, overwrite_update_file_again_commit) } + let(:change_diff_refs) { diff_refs(update_file_again_commit, overwrite_update_file_again_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 + Z + # 2 + Z + # 3 + Z + # 1 4 A + # 2 - B + # 5 + BB + # 3 6 C + # 4 7 D + # 5 8 E + # 6 - F + # 9 + FF + # 0 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 } # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 5 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 9 }, # + FF + { new_path: file_name, new_line: 10 } # + G + ] + + expect_new_positions(old_position_attrs, new_position_attrs) + end + end + + describe "merge of target branch" do + let(:merge_commit) do + second_create_file_commit + + merge_request = create(:merge_request, source_branch: second_branch_name, target_branch: branch_name, source_project: project) + + repository.merge(current_user, merge_request.diff_head_sha, merge_request, "Merge branches") + + project.commit(branch_name) + end + + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, merge_commit) } + let(:change_diff_refs) { diff_refs(update_file_again_commit, merge_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 + Z + # 2 + Z + # 3 + Z + # 1 4 A + # 2 - B + # 5 + BB + # 3 6 C + # 4 7 D + # 5 8 E + # 6 - F + # 9 + FF + # 0 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 } # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 5 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 9 }, # + FF + { new_path: file_name, new_line: 10 } # + G + ] + + expect_new_positions(old_position_attrs, new_position_attrs) + end + end + + describe "changing target branch" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 1 A + # 2 + BB + # 2 3 C + # 3 - DD + # 4 + D + # 4 5 E + # 5 - F + # 6 + FF + # 7 G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 } # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2, change: true }, + { new_path: file_name, new_line: 2 }, + { old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 }, + { new_path: file_name, new_line: 4 }, + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 5 }, + { old_path: file_name, old_line: 5 }, + { new_path: file_name, new_line: 6 }, + { new_path: file_name, new_line: 7 } + ] + + expect_new_positions(old_position_attrs, new_position_attrs) + end + end + end + end +end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index 866550753a8..79b33d4d276 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -1,1896 +1,98 @@ require 'spec_helper' describe Gitlab::Diff::PositionTracer do - # Douwe's diary New York City, 2016-06-28 - # -------------------------------------------------------------------------- - # - # Dear diary, - # - # Ideally, we would have a test for every single diff scenario that can - # occur and that the PositionTracer should correctly trace a position - # through, across the following variables: - # - # - Old diff file type: created, changed, renamed, deleted, unchanged (5) - # - Old diff line type: added, removed, unchanged (3) - # - New diff file type: created, changed, renamed, deleted, unchanged (5) - # - New diff line type: added, removed, unchanged (3) - # - Old-to-new diff line change: kept, moved, undone (3) - # - # This adds up to 5 * 3 * 5 * 3 * 3 = 675 different potential scenarios, - # and 675 different tests to cover them all. In reality, it would be fewer, - # since one cannot have a removed line in a created file diff, for example, - # but for the sake of this diary entry, let's be pessimistic. - # - # Writing these tests is a manual and time consuming process, as every test - # requires the manual construction or finding of a combination of diffs that - # create the exact diff scenario we are looking for, and can take between - # 1 and 10 minutes, depending on the farfetchedness of the scenario and - # complexity of creating it. - # - # This means that writing tests to cover all of these scenarios would end up - # taking between 11 and 112 hours in total, which I do not believe is the - # best use of my time. - # - # A better course of action would be to think of scenarios that are likely - # to occur, but also potentially tricky to trace correctly, and only cover - # those, with a few more obvious scenarios thrown in to cover our bases. - # - # Unfortunately, I only came to the above realization once I was about - # 1/5th of the way through the process of writing ALL THE SPECS, having - # already wasted about 3 hours trying to be thorough. - # - # I did find 2 bugs while writing those though, so that's good. - # - # In any case, all of this means that the tests below will be extremely - # (excessively, unjustifiably) thorough for scenarios where "the file was - # created in the old diff" and then drop off to comparatively lackluster - # testing of other scenarios. - # - # I did still try to cover most of the obvious and potentially tricky - # scenarios, though. + include PositionTracerHelpers - include RepoHelpers - - let(:project) { create(:project, :repository) } - let(:current_user) { project.owner } - let(:repository) { project.repository } - let(:file_name) { "test-file" } - let(:new_file_name) { "#{file_name}-new" } - let(:second_file_name) { "#{file_name}-2" } - let(:branch_name) { "position-tracer-test" } - - let(:old_diff_refs) { raise NotImplementedError } - let(:new_diff_refs) { raise NotImplementedError } - let(:change_diff_refs) { raise NotImplementedError } - let(:old_position) { raise NotImplementedError } - - let(:position_tracer) { described_class.new(project: project, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) } - subject { position_tracer.trace(old_position) } - - def diff_refs(base_commit, head_commit) - Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id) - end - - def text_position_attrs - [:old_line, :new_line] - end - - def position(attrs = {}) - attrs.reverse_merge!( - diff_refs: old_diff_refs + subject do + described_class.new( + project: project, + old_diff_refs: old_diff_refs, + new_diff_refs: new_diff_refs ) - Gitlab::Diff::Position.new(attrs) end - def expect_new_position(attrs, result = subject) - aggregate_failures("expect new position #{attrs.inspect}") do - if attrs.nil? - expect(result[:outdated]).to be_truthy - else - expect(result[:outdated]).to be_falsey + describe '#trace' do + let(:diff_refs) { double(complete?: true) } + let(:project) { double } + let(:old_diff_refs) { diff_refs } + let(:new_diff_refs) { diff_refs } + let(:position) { double(on_text?: on_text?, diff_refs: diff_refs) } + let(:tracer) { double } - new_position = result[:position] - expect(new_position).not_to be_nil + context 'position is on text' do + let(:on_text?) { true } - expect(new_position.diff_refs).to eq(new_diff_refs) + it 'calls LineStrategy#trace' do + expect(Gitlab::Diff::PositionTracer::LineStrategy) + .to receive(:new) + .with(subject) + .and_return(tracer) + expect(tracer).to receive(:trace).with(position) - attrs.each do |attr, value| - if text_position_attrs.include?(attr) - expect(new_position.formatter.send(attr)).to eq(value) - else - expect(new_position.send(attr)).to eq(value) - end - end + subject.trace(position) end end - end - - def expect_change_position(attrs, result = subject) - aggregate_failures("expect change position #{attrs.inspect}") do - expect(result[:outdated]).to be_truthy - - change_position = result[:position] - if attrs.nil? || attrs.empty? - expect(change_position).to be_nil - else - expect(change_position).not_to be_nil - - expect(change_position.diff_refs).to eq(change_diff_refs) - - attrs.each do |attr, value| - if text_position_attrs.include?(attr) - expect(change_position.formatter.send(attr)).to eq(value) - else - expect(change_position.send(attr)).to eq(value) - end - end - end - end - end - - def create_branch(new_name, branch_name) - CreateBranchService.new(project, current_user).execute(new_name, branch_name) - end - - def create_file(branch_name, file_name, content) - Files::CreateService.new( - project, - current_user, - start_branch: branch_name, - branch_name: branch_name, - commit_message: "Create file", - file_path: file_name, - file_content: content - ).execute - project.commit(branch_name) - end - - def update_file(branch_name, file_name, content) - Files::UpdateService.new( - project, - current_user, - start_branch: branch_name, - branch_name: branch_name, - commit_message: "Update file", - file_path: file_name, - file_content: content - ).execute - project.commit(branch_name) - end - - def delete_file(branch_name, file_name) - Files::DeleteService.new( - project, - current_user, - start_branch: branch_name, - branch_name: branch_name, - commit_message: "Delete file", - file_path: file_name - ).execute - project.commit(branch_name) - end - - let(:initial_commit) do - create_branch(branch_name, "master")[:branch].name - project.commit(branch_name) - end - - describe "#trace" do - describe "diff scenarios" do - let(:create_file_commit) do - initial_commit - - create_file( - branch_name, - file_name, - <<-CONTENT.strip_heredoc - A - B - C - CONTENT - ) - end - - let(:create_second_file_commit) do - create_file_commit - - create_file( - branch_name, - second_file_name, - <<-CONTENT.strip_heredoc - D - E - CONTENT - ) - end - - let(:update_line_commit) do - create_second_file_commit - - update_file( - branch_name, - file_name, - <<-CONTENT.strip_heredoc - A - BB - C - CONTENT - ) - end - - let(:update_second_file_line_commit) do - update_line_commit - - update_file( - branch_name, - second_file_name, - <<-CONTENT.strip_heredoc - D - EE - CONTENT - ) - end - - let(:move_line_commit) do - update_second_file_line_commit - - update_file( - branch_name, - file_name, - <<-CONTENT.strip_heredoc - BB - A - C - CONTENT - ) - end - - let(:add_second_file_line_commit) do - move_line_commit - - update_file( - branch_name, - second_file_name, - <<-CONTENT.strip_heredoc - D - EE - F - CONTENT - ) - end - - let(:move_second_file_line_commit) do - add_second_file_line_commit - - update_file( - branch_name, - second_file_name, - <<-CONTENT.strip_heredoc - D - F - EE - CONTENT - ) - end - - let(:delete_line_commit) do - move_second_file_line_commit - - update_file( - branch_name, - file_name, - <<-CONTENT.strip_heredoc - BB - A - CONTENT - ) - end - - let(:delete_second_file_line_commit) do - delete_line_commit - - update_file( - branch_name, - second_file_name, - <<-CONTENT.strip_heredoc - D - F - CONTENT - ) - end - - let(:delete_file_commit) do - delete_second_file_line_commit - - delete_file(branch_name, file_name) - end - - let(:rename_file_commit) do - delete_file_commit - - create_file( - branch_name, - new_file_name, - <<-CONTENT.strip_heredoc - BB - A - CONTENT - ) - end - - let(:update_line_again_commit) do - rename_file_commit - - update_file( - branch_name, - new_file_name, - <<-CONTENT.strip_heredoc - BB - AA - CONTENT - ) - end - - let(:move_line_again_commit) do - update_line_again_commit - - update_file( - branch_name, - new_file_name, - <<-CONTENT.strip_heredoc - AA - BB - CONTENT - ) - end - - let(:delete_line_again_commit) do - move_line_again_commit - - update_file( - branch_name, - new_file_name, - <<-CONTENT.strip_heredoc - AA - CONTENT - ) - end - - context "when the file was created in the old diff" do - context "when the file is created in the new diff" do - context "when the position pointed at an added line in the old diff" do - context "when the file's content was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + B - # 3 + C - # - # new diff: - # 1 + A - # 2 + B - # 3 + C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - new_line: old_position.new_line - ) - end - end - - context "when the file's content was changed between the old and the new diff" do - context "when that line was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 1) } - - # old diff: - # 1 + A - # 2 + B - # 3 + C - # - # new diff: - # 1 + A - # 2 + BB - # 3 + C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - new_line: old_position.new_line - ) - end - end - - context "when that line was moved between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + BB - # 3 + C - # - # new diff: - # 1 + BB - # 2 + A - # 3 + C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - new_line: 1 - ) - end - end - - context "when that line was changed between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + B - # 3 + C - # - # new diff: - # 1 + A - # 2 + BB - # 3 + C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 2, - new_line: nil - ) - end - end - - context "when that line was deleted between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } - let(:change_diff_refs) { diff_refs(update_line_commit, delete_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 3) } - - # old diff: - # 1 + A - # 2 + BB - # 3 + C - # - # new diff: - # 1 + A - # 2 + BB - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 3, - new_line: nil - ) - end - end - end - end - end - - context "when the file is changed in the new diff" do - context "when the position pointed at an added line in the old diff" do - context "when the file's content was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 1) } - - # old diff: - # 1 + A - # 2 + BB - # 3 + C - # - # new diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - new_line: old_position.new_line - ) - end - end - - context "when the file's content was changed between the old and the new diff" do - context "when that line was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 3) } - - # old diff: - # 1 + A - # 2 + BB - # 3 + C - # - # new diff: - # 1 - A - # 2 1 BB - # 2 + A - # 3 3 C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - new_line: old_position.new_line - ) - end - end - - context "when that line was moved between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + BB - # 3 + C - # - # new diff: - # 1 - A - # 2 1 BB - # 2 + A - # 3 3 C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - new_line: 1 - ) - end - end - - context "when that line was changed between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + B - # 3 + C - # - # new diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 2, - new_line: nil - ) - end - end - - context "when that line was deleted between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } - let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 3) } - - # old diff: - # 1 + BB - # 2 + A - # 3 + C - # - # new diff: - # 1 1 BB - # 2 2 A - # 3 - C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 3, - new_line: nil - ) - end - end - end - end - end - - context "when the file is renamed in the new diff" do - context "when the position pointed at an added line in the old diff" do - context "when the file's content was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } - let(:new_diff_refs) { diff_refs(delete_line_commit, rename_file_commit) } - let(:change_diff_refs) { diff_refs(initial_commit, delete_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + BB - # 2 + A - # - # new diff: - # file_name -> new_file_name - # 1 1 BB - # 2 2 A - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: nil, - new_line: 2 - ) - end - end - - context "when the file's content was changed between the old and the new diff" do - context "when that line was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } - let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } - let(:old_position) { position(new_path: file_name, new_line: 1) } - - # old diff: - # 1 + BB - # 2 + A - # - # new diff: - # file_name -> new_file_name - # 1 1 BB - # 2 - A - # 2 + AA - - it "returns the new position" do - expect_new_position( - old_path: file_name, - new_path: new_file_name, - old_line: old_position.new_line, - new_line: old_position.new_line - ) - end - end - - context "when that line was moved between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } - let(:new_diff_refs) { diff_refs(delete_line_commit, move_line_again_commit) } - let(:old_position) { position(new_path: file_name, new_line: 1) } - - # old diff: - # 1 + BB - # 2 + A - # - # new diff: - # file_name -> new_file_name - # 1 + AA - # 1 2 BB - # 2 - A - - it "returns the new position" do - expect_new_position( - old_path: file_name, - new_path: new_file_name, - old_line: 1, - new_line: 2 - ) - end - end - - context "when that line was changed between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } - let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } - let(:change_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + BB - # 2 + A - # - # new diff: - # file_name -> new_file_name - # 1 1 BB - # 2 - A - # 2 + AA - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: new_file_name, - old_line: 2, - new_line: nil - ) - end - end - end - end - end - - context "when the file is deleted in the new diff" do - context "when the position pointed at an added line in the old diff" do - context "when the file's content was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } - let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } - let(:change_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + BB - # 2 + A - # - # new diff: - # 1 - BB - # 2 - A - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 2, - new_line: nil - ) - end - end - - context "when the file's content was changed between the old and the new diff" do - context "when that line was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } - let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + BB - # 2 + A - # 3 + C - # - # new diff: - # 1 - BB - # 2 - A - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 2, - new_line: nil - ) - end - end - - context "when that line was moved between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } - let(:change_diff_refs) { diff_refs(update_line_commit, delete_file_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + BB - # 3 + C - # - # new diff: - # 1 - BB - # 2 - A - # 3 - C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 2, - new_line: nil - ) - end - end - - context "when that line was changed between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:new_diff_refs) { diff_refs(update_line_commit, delete_file_commit) } - let(:change_diff_refs) { diff_refs(create_file_commit, delete_file_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + B - # 3 + C - # - # new diff: - # 1 - A - # 2 - BB - # 3 - C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 2, - new_line: nil - ) - end - end - - context "when that line was deleted between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } - let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } - let(:old_position) { position(new_path: file_name, new_line: 3) } - # old diff: - # 1 + BB - # 2 + A - # 3 + C - # - # new diff: - # 1 - BB - # 2 - A + context 'position is not on text' do + let(:on_text?) { false } - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 3, - new_line: nil - ) - end - end - end - end - end + it 'calls ImageStrategy#trace' do + expect(Gitlab::Diff::PositionTracer::ImageStrategy) + .to receive(:new) + .with(subject) + .and_return(tracer) + expect(tracer).to receive(:trace).with(position) - context "when the file is unchanged in the new diff" do - context "when the position pointed at an added line in the old diff" do - context "when the file's content was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) } - let(:change_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + B - # 3 + C - # - # new diff: - # 1 1 A - # 2 2 B - # 3 3 C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: nil, - new_line: 2 - ) - end - end - - context "when the file's content was changed between the old and the new diff" do - context "when that line was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } - let(:change_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 1) } - - # old diff: - # 1 + A - # 2 + B - # 3 + C - # - # new diff: - # 1 1 A - # 2 2 BB - # 3 3 C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: nil, - new_line: 1 - ) - end - end - - context "when that line was moved between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(move_line_commit, move_second_file_line_commit) } - let(:change_diff_refs) { diff_refs(initial_commit, move_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + BB - # 3 + C - # - # new diff: - # 1 1 BB - # 2 2 A - # 3 3 C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: nil, - new_line: 1 - ) - end - end - - context "when that line was changed between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } - let(:change_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 2) } - - # old diff: - # 1 + A - # 2 + B - # 3 + C - # - # new diff: - # 1 1 A - # 2 2 BB - # 3 3 C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 2, - new_line: nil - ) - end - end - - context "when that line was deleted between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(delete_line_commit, delete_second_file_line_commit) } - let(:change_diff_refs) { diff_refs(move_line_commit, delete_second_file_line_commit) } - let(:old_position) { position(new_path: file_name, new_line: 3) } - - # old diff: - # 1 + BB - # 2 + A - # 3 + C - # - # new diff: - # 1 1 BB - # 2 2 A - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 3, - new_line: nil - ) - end - end - end - end - end - end - - context "when the file was changed in the old diff" do - context "when the file is created in the new diff" do - context "when the position pointed at an added line in the old diff" do - context "when the file's content was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # - # new diff: - # 1 + A - # 2 + BB - # 3 + C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - old_line: nil, - new_line: old_position.new_line - ) - end - end - - context "when the file's content was changed between the old and the new diff" do - context "when that line was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } - - # old diff: - # 1 + BB - # 1 2 A - # 2 - B - # 3 3 C - # - # new diff: - # 1 + BB - # 2 + A - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - old_line: nil, - new_line: old_position.new_line - ) - end - end - - context "when that line was moved between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # - # new diff: - # 1 + BB - # 2 + A - # 3 + C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - old_line: nil, - new_line: 1 - ) - end - end - - context "when that line was changed or deleted between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, create_file_commit) } - let(:change_diff_refs) { diff_refs(move_line_commit, create_file_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } - - # old diff: - # 1 + BB - # 1 2 A - # 2 - B - # 3 3 C - # - # new diff: - # 1 + A - # 2 + B - # 3 + C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 1, - new_line: nil - ) - end - end - end - end - - context "when the position pointed at a deleted line in the old diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:change_diff_refs) { diff_refs(create_file_commit, initial_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # - # new diff: - # 1 + A - # 2 + BB - # 3 + C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 2, - new_line: nil - ) - end - end - - context "when the position pointed at an unchanged line in the old diff" do - context "when the file's content was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 1) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # - # new diff: - # 1 + A - # 2 + BB - # 3 + C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - old_line: nil, - new_line: old_position.new_line - ) - end - end - - context "when the file's content was changed between the old and the new diff" do - context "when that line was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 2) } - - # old diff: - # 1 + BB - # 1 2 A - # 2 - B - # 3 3 C - # - # new diff: - # 1 + BB - # 2 + A - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - old_line: nil, - new_line: old_position.new_line - ) - end - end - - context "when that line was moved between the old and the new diff" do - let(:old_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2, new_line: 2) } - - # old diff: - # 1 1 BB - # 2 2 A - # 3 - C - # - # new diff: - # 1 + A - # 2 + BB - # 3 + C - - it "returns the new position" do - expect_new_position( - new_path: old_position.new_path, - old_line: nil, - new_line: 1 - ) - end - end - - context "when that line was changed or deleted between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } - let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 3, new_line: 3) } - - # old diff: - # 1 + BB - # 1 2 A - # 2 - B - # 3 3 C - # - # new diff: - # 1 + A - # 2 + B - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 3, - new_line: nil - ) - end - end - end - end - end - - context "when the file is changed in the new diff" do - context "when the position pointed at an added line in the old diff" do - context "when the file's content was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # - # new diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - - it "returns the new position" do - expect_new_position( - old_path: old_position.old_path, - new_path: old_position.new_path, - old_line: nil, - new_line: old_position.new_line - ) - end - end - - context "when the file's content was changed between the old and the new diff" do - context "when that line was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } - - # old diff: - # 1 + BB - # 1 2 A - # 2 - B - # 3 3 C - # - # new diff: - # 1 1 BB - # 2 2 A - # 3 - C - - it "returns the new position" do - expect_new_position( - old_path: old_position.old_path, - new_path: old_position.new_path, - old_line: 1, - new_line: 1 - ) - end - end - - context "when that line was moved between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # - # new diff: - # 1 - A - # 2 1 BB - # 2 + A - # 3 3 C - - it "returns the new position" do - expect_new_position( - old_path: old_position.old_path, - new_path: old_position.new_path, - old_line: 2, - new_line: 1 - ) - end - end - - context "when that line was changed or deleted between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:change_diff_refs) { diff_refs(move_line_commit, update_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } - - # old diff: - # 1 + BB - # 1 2 A - # 2 - B - # 3 3 C - # - # new diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - - it "returns the position of the change" do - expect_change_position( - old_path: file_name, - new_path: file_name, - old_line: 1, - new_line: nil - ) - end - end - end - end - - context "when the position pointed at a deleted line in the old diff" do - context "when the file's content was unchanged between the old and the new diff" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } - let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # - # new diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - - it "returns the new position" do - expect_new_position( - old_path: old_position.old_path, - new_path: old_position.new_path, - old_line: old_position.old_line, - new_line: nil - ) - end - end - end - end + subject.trace(position) end end end - describe "typical use scenarios" do - let(:second_branch_name) { "#{branch_name}-2" } - - def expect_new_positions(old_attrs, new_attrs) - old_positions = old_attrs.map do |old_attrs| - position(old_attrs) - end + describe 'diffs methods' do + let(:project) { create(:project, :repository) } + let(:current_user) { project.owner } - new_positions = old_positions.map do |old_position| - position_tracer.trace(old_position) - end - - aggregate_failures do - new_positions.zip(new_attrs).each do |new_position, new_attrs| - if new_attrs&.delete(:change) - expect_change_position(new_attrs, new_position) - else - expect_new_position(new_attrs, new_position) - end - end - end - end - - let(:create_file_commit) do - initial_commit - - create_file( - branch_name, - file_name, - <<-CONTENT.strip_heredoc - A - B - C - D - E - F - CONTENT - ) - end - - let(:second_create_file_commit) do - create_file_commit - - create_branch(second_branch_name, branch_name) - - update_file( - second_branch_name, - file_name, - <<-CONTENT.strip_heredoc - Z - Z - Z - A - B - C - D - E - F - CONTENT + let(:old_diff_refs) do + diff_refs( + project.commit(create_branch('new-branch', 'master')[:branch].name), + create_file('new-branch', 'file.md', 'content') ) end - let(:update_file_commit) do - second_create_file_commit - - update_file( - branch_name, - file_name, - <<-CONTENT.strip_heredoc - A - C - DD - E - F - G - CONTENT - ) - end - - let(:update_file_again_commit) do - update_file_commit - - update_file( - branch_name, - file_name, - <<-CONTENT.strip_heredoc - A - BB - C - D - E - FF - G - CONTENT + let(:new_diff_refs) do + diff_refs( + create_file('new-branch', 'file.md', 'content'), + update_file('new-branch', 'file.md', 'updatedcontent') ) end - describe "simple push of new commit" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } - let(:change_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) } - - # old diff: - # 1 1 A - # 2 - B - # 3 2 C - # 4 - D - # 3 + DD - # 5 4 E - # 6 5 F - # 6 + G - # - # new diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # 4 4 D - # 5 5 E - # 6 - F - # 6 + FF - # 7 + G - - it "returns the new positions" do - old_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A - { old_path: file_name, old_line: 2 }, # - B - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C - { old_path: file_name, old_line: 4 }, # - D - { new_path: file_name, new_line: 3 }, # + DD - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E - { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F - { new_path: file_name, new_line: 6 }, # + G - ] - - new_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, - { old_path: file_name, old_line: 2 }, - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, - { new_path: file_name, new_line: 4, change: true }, - { new_path: file_name, old_line: 3, change: true }, - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, - { new_path: file_name, old_line: 5, change: true }, - { new_path: file_name, new_line: 7 } - ] + describe '#ac_diffs' do + it 'returns the diffs between the base of old and new diff' do + diff_refs = subject.ac_diffs.diff_refs - expect_new_positions(old_position_attrs, new_position_attrs) + expect(diff_refs.base_sha).to eq(old_diff_refs.base_sha) + expect(diff_refs.start_sha).to eq(old_diff_refs.base_sha) + expect(diff_refs.head_sha).to eq(new_diff_refs.base_sha) end end - describe "force push to overwrite last commit" do - let(:second_create_file_commit) do - create_file_commit + describe '#bd_diffs' do + it 'returns the diffs between the HEAD of old and new diff' do + diff_refs = subject.bd_diffs.diff_refs - create_branch(second_branch_name, branch_name) - - update_file( - second_branch_name, - file_name, - <<-CONTENT.strip_heredoc - A - BB - C - D - E - FF - G - CONTENT - ) - end - - let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, second_create_file_commit) } - let(:change_diff_refs) { diff_refs(update_file_commit, second_create_file_commit) } - - # old diff: - # 1 1 A - # 2 - B - # 3 2 C - # 4 - D - # 3 + DD - # 5 4 E - # 6 5 F - # 6 + G - # - # new diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # 4 4 D - # 5 5 E - # 6 - F - # 6 + FF - # 7 + G - - it "returns the new positions" do - old_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A - { old_path: file_name, old_line: 2 }, # - B - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C - { old_path: file_name, old_line: 4 }, # - D - { new_path: file_name, new_line: 3 }, # + DD - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E - { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F - { new_path: file_name, new_line: 6 }, # + G - ] - - new_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, - { old_path: file_name, old_line: 2 }, - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, - { new_path: file_name, new_line: 4, change: true }, - { old_path: file_name, old_line: 3, change: true }, - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, - { old_path: file_name, old_line: 5, change: true }, - { new_path: file_name, new_line: 7 } - ] - - expect_new_positions(old_position_attrs, new_position_attrs) + expect(diff_refs.base_sha).to eq(old_diff_refs.head_sha) + expect(diff_refs.start_sha).to eq(old_diff_refs.head_sha) + expect(diff_refs.head_sha).to eq(new_diff_refs.head_sha) end end - describe "force push to delete last commit" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, update_file_commit) } - let(:change_diff_refs) { diff_refs(update_file_again_commit, update_file_commit) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # 4 4 D - # 5 5 E - # 6 - F - # 6 + FF - # 7 + G - # - # new diff: - # 1 1 A - # 2 - B - # 3 2 C - # 4 - D - # 3 + DD - # 5 4 E - # 6 5 F - # 6 + G - - it "returns the new positions" do - old_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A - { old_path: file_name, old_line: 2 }, # - B - { new_path: file_name, new_line: 2 }, # + BB - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C - { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E - { old_path: file_name, old_line: 6 }, # - F - { new_path: file_name, new_line: 6 }, # + FF - { new_path: file_name, new_line: 7 }, # + G - ] - - new_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, - { old_path: file_name, old_line: 2 }, - { old_path: file_name, old_line: 2, change: true }, - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, - { old_path: file_name, old_line: 4, change: true }, - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, - { new_path: file_name, new_line: 5, change: true }, - { old_path: file_name, old_line: 6, change: true }, - { new_path: file_name, new_line: 6 } - ] - - expect_new_positions(old_position_attrs, new_position_attrs) - end - end - - describe "rebase on top of target branch" do - let(:second_update_file_commit) do - update_file_commit - - update_file( - second_branch_name, - file_name, - <<-CONTENT.strip_heredoc - Z - Z - Z - A - C - DD - E - F - G - CONTENT - ) - end - - let(:update_file_again_commit) do - second_update_file_commit - - update_file( - branch_name, - file_name, - <<-CONTENT.strip_heredoc - A - BB - C - D - E - FF - G - CONTENT - ) - end - - let(:overwrite_update_file_again_commit) do - update_file_again_commit - - update_file( - second_branch_name, - file_name, - <<-CONTENT.strip_heredoc - Z - Z - Z - A - BB - C - D - E - FF - G - CONTENT - ) - end - - let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, overwrite_update_file_again_commit) } - let(:change_diff_refs) { diff_refs(update_file_again_commit, overwrite_update_file_again_commit) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # 4 4 D - # 5 5 E - # 6 - F - # 6 + FF - # 7 + G - # - # new diff: - # 1 + Z - # 2 + Z - # 3 + Z - # 1 4 A - # 2 - B - # 5 + BB - # 3 6 C - # 4 7 D - # 5 8 E - # 6 - F - # 9 + FF - # 0 + G - - it "returns the new positions" do - old_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A - { old_path: file_name, old_line: 2 }, # - B - { new_path: file_name, new_line: 2 }, # + BB - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C - { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E - { old_path: file_name, old_line: 6 }, # - F - { new_path: file_name, new_line: 6 }, # + FF - { new_path: file_name, new_line: 7 }, # + G - ] - - new_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A - { old_path: file_name, old_line: 2 }, # - B - { new_path: file_name, new_line: 5 }, # + BB - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C - { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E - { old_path: file_name, old_line: 6 }, # - F - { new_path: file_name, new_line: 9 }, # + FF - { new_path: file_name, new_line: 10 }, # + G - ] - - expect_new_positions(old_position_attrs, new_position_attrs) - end - end - - describe "merge of target branch" do - let(:merge_commit) do - second_create_file_commit - - merge_request = create(:merge_request, source_branch: second_branch_name, target_branch: branch_name, source_project: project) - - repository.merge(current_user, merge_request.diff_head_sha, merge_request, "Merge branches") - - project.commit(branch_name) - end - - let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } - let(:new_diff_refs) { diff_refs(create_file_commit, merge_commit) } - let(:change_diff_refs) { diff_refs(update_file_again_commit, merge_commit) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # 4 4 D - # 5 5 E - # 6 - F - # 6 + FF - # 7 + G - # - # new diff: - # 1 + Z - # 2 + Z - # 3 + Z - # 1 4 A - # 2 - B - # 5 + BB - # 3 6 C - # 4 7 D - # 5 8 E - # 6 - F - # 9 + FF - # 0 + G - - it "returns the new positions" do - old_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A - { old_path: file_name, old_line: 2 }, # - B - { new_path: file_name, new_line: 2 }, # + BB - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C - { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E - { old_path: file_name, old_line: 6 }, # - F - { new_path: file_name, new_line: 6 }, # + FF - { new_path: file_name, new_line: 7 }, # + G - ] - - new_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A - { old_path: file_name, old_line: 2 }, # - B - { new_path: file_name, new_line: 5 }, # + BB - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C - { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E - { old_path: file_name, old_line: 6 }, # - F - { new_path: file_name, new_line: 9 }, # + FF - { new_path: file_name, new_line: 10 }, # + G - ] - - expect_new_positions(old_position_attrs, new_position_attrs) - end - end - - describe "changing target branch" do - let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } - let(:new_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) } - let(:change_diff_refs) { diff_refs(create_file_commit, update_file_commit) } - - # old diff: - # 1 1 A - # 2 - B - # 2 + BB - # 3 3 C - # 4 4 D - # 5 5 E - # 6 - F - # 6 + FF - # 7 + G - # - # new diff: - # 1 1 A - # 2 + BB - # 2 3 C - # 3 - DD - # 4 + D - # 4 5 E - # 5 - F - # 6 + FF - # 7 G - - it "returns the new positions" do - old_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A - { old_path: file_name, old_line: 2 }, # - B - { new_path: file_name, new_line: 2 }, # + BB - { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C - { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D - { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E - { old_path: file_name, old_line: 6 }, # - F - { new_path: file_name, new_line: 6 }, # + FF - { new_path: file_name, new_line: 7 }, # + G - ] - - new_position_attrs = [ - { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, - { old_path: file_name, old_line: 2, change: true }, - { new_path: file_name, new_line: 2 }, - { old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 }, - { new_path: file_name, new_line: 4 }, - { old_path: file_name, new_path: file_name, old_line: 4, new_line: 5 }, - { old_path: file_name, old_line: 5 }, - { new_path: file_name, new_line: 6 }, - { new_path: file_name, new_line: 7 } - ] + describe '#cd_diffs' do + it 'returns the diffs in the new diff' do + diff_refs = subject.cd_diffs.diff_refs - expect_new_positions(old_position_attrs, new_position_attrs) + expect(diff_refs.base_sha).to eq(new_diff_refs.base_sha) + expect(diff_refs.start_sha).to eq(new_diff_refs.base_sha) + expect(diff_refs.head_sha).to eq(new_diff_refs.head_sha) end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index cceeae8afe6..a28b95e5bff 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1694,14 +1694,15 @@ describe Gitlab::Git::Repository, :seed_helper do let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } let(:left_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } let(:right_branch) { 'test-master' } + let(:first_parent_ref) { 'refs/heads/test-master' } let(:target_ref) { 'refs/merge-requests/999/merge' } before do - repository.create_branch(right_branch, branch_head) unless repository.branch_exists?(right_branch) + repository.create_branch(right_branch, branch_head) unless repository.ref_exists?(first_parent_ref) end def merge_to_ref - repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message') + repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message', first_parent_ref) end it 'generates a commit in the target_ref' do @@ -1716,7 +1717,7 @@ describe Gitlab::Git::Repository, :seed_helper do end it 'does not change the right branch HEAD' do - expect { merge_to_ref }.not_to change { repository.find_branch(right_branch).target } + expect { merge_to_ref }.not_to change { repository.commit(first_parent_ref).sha } end end diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index 18663a72fcd..f38b8d31237 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -79,13 +79,13 @@ describe Gitlab::GitalyClient::OperationService do end describe '#user_merge_to_ref' do - let(:branch) { 'my-branch' } + let(:first_parent_ref) { 'refs/heads/my-branch' } let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } let(:ref) { 'refs/merge-requests/x/merge' } let(:message) { 'validación' } let(:response) { Gitaly::UserMergeToRefResponse.new(commit_id: 'new-commit-id') } - subject { client.user_merge_to_ref(user, source_sha, branch, ref, message) } + subject { client.user_merge_to_ref(user, source_sha, nil, ref, message, first_parent_ref) } it 'sends a user_merge_to_ref message' do expect_any_instance_of(Gitaly::OperationService::Stub) diff --git a/spec/models/clusters/clusters_hierarchy_spec.rb b/spec/models/clusters/clusters_hierarchy_spec.rb new file mode 100644 index 00000000000..0470ebe17ea --- /dev/null +++ b/spec/models/clusters/clusters_hierarchy_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::ClustersHierarchy do + describe '#base_and_ancestors' do + def base_and_ancestors(clusterable) + described_class.new(clusterable).base_and_ancestors + end + + context 'project in nested group with clusters at every level' do + let!(:cluster) { create(:cluster, :project, projects: [project]) } + let!(:child) { create(:cluster, :group, groups: [child_group]) } + let!(:parent) { create(:cluster, :group, groups: [parent_group]) } + let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group]) } + + let(:ancestor_group) { create(:group) } + let(:parent_group) { create(:group, parent: ancestor_group) } + let(:child_group) { create(:group, parent: parent_group) } + let(:project) { create(:project, group: child_group) } + + it 'returns clusters for project' do + expect(base_and_ancestors(project)).to eq([cluster, child, parent, ancestor]) + end + + it 'returns clusters for child_group' do + expect(base_and_ancestors(child_group)).to eq([child, parent, ancestor]) + end + + it 'returns clusters for parent_group' do + expect(base_and_ancestors(parent_group)).to eq([parent, ancestor]) + end + + it 'returns clusters for ancestor_group' do + expect(base_and_ancestors(ancestor_group)).to eq([ancestor]) + end + end + + context 'project in a namespace' do + let!(:cluster) { create(:cluster, :project) } + + it 'returns clusters for project' do + expect(base_and_ancestors(cluster.project)).to eq([cluster]) + end + end + + context 'project in nested group with clusters at some levels' do + let!(:child) { create(:cluster, :group, groups: [child_group]) } + let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group]) } + + let(:ancestor_group) { create(:group) } + let(:parent_group) { create(:group, parent: ancestor_group) } + let(:child_group) { create(:group, parent: parent_group) } + let(:project) { create(:project, group: child_group) } + + it 'returns clusters for project' do + expect(base_and_ancestors(project)).to eq([child, ancestor]) + end + + it 'returns clusters for child_group' do + expect(base_and_ancestors(child_group)).to eq([child, ancestor]) + end + + it 'returns clusters for parent_group' do + expect(base_and_ancestors(parent_group)).to eq([ancestor]) + end + + it 'returns clusters for ancestor_group' do + expect(base_and_ancestors(ancestor_group)).to eq([ancestor]) + end + end + end +end diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb index 2378f400540..e2fc8a5d127 100644 --- a/spec/models/concerns/deployment_platform_spec.rb +++ b/spec/models/concerns/deployment_platform_spec.rb @@ -5,7 +5,7 @@ require 'rails_helper' describe DeploymentPlatform do let(:project) { create(:project) } - describe '#deployment_platform' do + shared_examples '#deployment_platform' do subject { project.deployment_platform } context 'with no Kubernetes configuration on CI/CD, no Kubernetes Service' do @@ -84,4 +84,20 @@ describe DeploymentPlatform do end end end + + context 'legacy implementation' do + before do + stub_feature_flags(clusters_cte: false) + end + + include_examples '#deployment_platform' + end + + context 'CTE implementation' do + before do + stub_feature_flags(clusters_cte: true) + end + + include_examples '#deployment_platform' + end end diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index 7faa196623f..e2ab9ddd4a5 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -206,8 +206,9 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do expect(read_reactive_cache(instance)).to eq("preexisting") end - it 'enqueues a repeat worker' do - expect_reactive_cache_update_queued(instance) + it 'does not enqueue a repeat worker' do + expect(ReactiveCachingWorker) + .not_to receive(:perform_in) expect { go! }.to raise_error("foo") end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 8d0eb0f4a06..713fb647708 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -462,12 +462,6 @@ describe Deployment do it { is_expected.to be_nil } end - context 'project uses the kubernetes service for deployments' do - let!(:service) { create(:kubernetes_service, project: project) } - - it { is_expected.to be_nil } - end - context 'project has a deployment platform' do let!(:cluster) { create(:cluster, projects: [project]) } let!(:platform) { create(:cluster_platform_kubernetes, cluster: cluster) } diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index 22df19d943f..a771d1bf27f 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -101,6 +101,15 @@ describe DroneCiService, :use_clean_rails_memory_store_caching do is_expected.to eq(:error) end + Gitlab::HTTP::HTTP_ERRORS.each do |http_error| + it "sets commit status to :error with a #{http_error.name} error" do + WebMock.stub_request(:get, commit_status_path) + .to_raise(http_error) + + is_expected.to eq(:error) + end + end + { "killed" => :canceled, "failure" => :failed, diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 5d7d6c34e67..d33bbb0470f 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -142,235 +142,6 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do end end - describe '#kubernetes_namespace_for' do - subject { service.kubernetes_namespace_for(project) } - - shared_examples 'a correctly formatted namespace' do - it 'returns a valid Kubernetes namespace name' do - expect(subject).to match(Gitlab::Regex.kubernetes_namespace_regex) - expect(subject).to eq(expected_namespace) - end - end - - it_behaves_like 'a correctly formatted namespace' do - let(:expected_namespace) { service.send(:default_namespace) } - end - - context 'when the project path contains forbidden characters' do - before do - project.path = '-a_Strange.Path--forSure' - end - - it_behaves_like 'a correctly formatted namespace' do - let(:expected_namespace) { "a-strange-path--forsure-#{project.id}" } - end - end - - context 'when namespace is specified' do - before do - service.namespace = 'my-namespace' - end - - it_behaves_like 'a correctly formatted namespace' do - let(:expected_namespace) { 'my-namespace' } - end - end - - context 'when service is not assigned to project' do - before do - service.project = nil - end - - it 'does not return namespace' do - is_expected.to be_nil - end - end - end - - describe '#test' do - let(:discovery_url) { 'https://kubernetes.example.com/api/v1' } - - before do - stub_kubeclient_discover(service.api_url) - end - - context 'with path prefix in api_url' do - let(:discovery_url) { 'https://kubernetes.example.com/prefix/api/v1' } - - it 'tests with the prefix' do - service.api_url = 'https://kubernetes.example.com/prefix' - stub_kubeclient_discover(service.api_url) - - expect(service.test[:success]).to be_truthy - expect(WebMock).to have_requested(:get, discovery_url).once - end - end - - context 'with custom CA certificate' do - it 'is added to the certificate store' do - service.ca_pem = "CA PEM DATA" - - cert = double("certificate") - expect(OpenSSL::X509::Certificate).to receive(:new).with(service.ca_pem).and_return(cert) - expect_any_instance_of(OpenSSL::X509::Store).to receive(:add_cert).with(cert) - - expect(service.test[:success]).to be_truthy - expect(WebMock).to have_requested(:get, discovery_url).once - end - end - - context 'success' do - it 'reads the discovery endpoint' do - expect(service.test[:success]).to be_truthy - expect(WebMock).to have_requested(:get, discovery_url).once - end - end - - context 'failure' do - it 'fails to read the discovery endpoint' do - WebMock.stub_request(:get, service.api_url + '/api/v1').to_return(status: 404) - - expect(service.test[:success]).to be_falsy - expect(WebMock).to have_requested(:get, discovery_url).once - end - end - end - - describe '#predefined_variable' do - let(:kubeconfig) do - config_file = expand_fixture_path('config/kubeconfig.yml') - config = YAML.load(File.read(config_file)) - config.dig('users', 0, 'user')['token'] = 'token' - config.dig('contexts', 0, 'context')['namespace'] = namespace - config.dig('clusters', 0, 'cluster')['certificate-authority-data'] = - Base64.strict_encode64('CA PEM DATA') - - YAML.dump(config) - end - - before do - subject.api_url = 'https://kube.domain.com' - subject.token = 'token' - subject.ca_pem = 'CA PEM DATA' - subject.project = project - end - - shared_examples 'setting variables' do - it 'sets the variables' do - expect(subject.predefined_variables(project: project)).to include( - { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true }, - { key: 'KUBE_TOKEN', value: 'token', public: false, masked: true }, - { key: 'KUBE_NAMESPACE', value: namespace, public: true }, - { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true }, - { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true }, - { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true } - ) - end - end - - context 'namespace is provided' do - let(:namespace) { 'my-project' } - - before do - subject.namespace = namespace - end - - it_behaves_like 'setting variables' - end - - context 'no namespace provided' do - let(:namespace) { subject.kubernetes_namespace_for(project) } - - it_behaves_like 'setting variables' - - it 'sets the KUBE_NAMESPACE' do - kube_namespace = subject.predefined_variables(project: project).find { |h| h[:key] == 'KUBE_NAMESPACE' } - - expect(kube_namespace).not_to be_nil - expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/) - end - end - end - - describe '#terminals' do - let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") } - - subject { service.terminals(environment) } - - context 'with invalid pods' do - it 'returns no terminals' do - stub_reactive_cache(service, pods: [{ "bad" => "pod" }]) - - is_expected.to be_empty - end - end - - context 'with valid pods' do - let(:pod) { kube_pod(environment_slug: environment.slug, namespace: service.kubernetes_namespace_for(project), project_slug: project.full_path_slug) } - let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") } - let(:terminals) { kube_terminals(service, pod) } - - before do - stub_reactive_cache( - service, - pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] - ) - end - - it 'returns terminals' do - is_expected.to eq(terminals + terminals) - end - - it 'uses max session time from settings' do - stub_application_setting(terminal_max_session_time: 600) - - times = subject.map { |terminal| terminal[:max_session_time] } - expect(times).to eq [600, 600, 600, 600] - end - end - end - - describe '#calculate_reactive_cache' do - subject { service.calculate_reactive_cache } - - let(:namespace) { service.kubernetes_namespace_for(project) } - - context 'when service is inactive' do - before do - service.active = false - end - - it { is_expected.to be_nil } - end - - context 'when kubernetes responds with valid pods' do - before do - stub_kubeclient_pods(namespace) - stub_kubeclient_deployments(namespace) # Used by EE - end - - it { is_expected.to include(pods: [kube_pod]) } - end - - context 'when kubernetes responds with 500s' do - before do - stub_kubeclient_pods(namespace, status: 500) - stub_kubeclient_deployments(namespace, status: 500) # Used by EE - end - - it { expect { subject }.to raise_error(Kubeclient::HttpError) } - end - - context 'when kubernetes responds with 404s' do - before do - stub_kubeclient_pods(namespace, status: 404) - stub_kubeclient_deployments(namespace, status: 404) # Used by EE - end - - it { is_expected.to include(pods: []) } - end - end - describe "#deprecated?" do let(:kubernetes_service) { create(:kubernetes_service) } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 13da7bd7407..3d967aa4ab8 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1420,12 +1420,13 @@ describe Repository do source_project: project) end - it 'writes merge of source and target to MR merge_ref_path' do + it 'writes merge of source SHA and first parent ref to MR merge_ref_path' do merge_commit_id = repository.merge_to_ref(user, merge_request.diff_head_sha, merge_request, merge_request.merge_ref_path, - 'Custom message') + 'Custom message', + merge_request.target_branch_ref) merge_commit = repository.commit(merge_commit_id) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 5548e3fd01a..f5ce3a3570e 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -584,6 +584,34 @@ describe API::Runners do end end + context 'when valid order_by is provided' do + context 'when sort order is not specified' do + it 'return jobs in descending order' do + get api("/runners/#{project_runner.id}/jobs?order_by=id", admin) + + 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(2) + expect(json_response.first).to include('id' => job_5.id) + end + end + + context 'when sort order is specified as asc' do + it 'return jobs sorted in ascending order' do + get api("/runners/#{project_runner.id}/jobs?order_by=id&sort=asc", admin) + + 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(2) + expect(json_response.first).to include('id' => job_4.id) + end + end + end + context 'when invalid status is provided' do it 'return 400' do get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin) @@ -591,6 +619,22 @@ describe API::Runners do expect(response).to have_gitlab_http_status(400) end end + + context 'when invalid order_by is provided' do + it 'return 400' do + get api("/runners/#{project_runner.id}/jobs?order_by=non-existing", admin) + + expect(response).to have_gitlab_http_status(400) + end + end + + context 'when invalid sort is provided' do + it 'return 400' do + get api("/runners/#{project_runner.id}/jobs?sort=non-existing", admin) + + expect(response).to have_gitlab_http_status(400) + end + end end context "when runner doesn't exist" do diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb index 24cb63a0d61..a409f21a7e4 100644 --- a/spec/services/auto_merge/base_service_spec.rb +++ b/spec/services/auto_merge/base_service_spec.rb @@ -121,11 +121,7 @@ describe AutoMerge::BaseService do end end - describe '#cancel' do - subject { service.cancel(merge_request) } - - let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } - + shared_examples_for 'Canceled or Dropped' do it 'removes properies from the merge request' do subject @@ -173,6 +169,20 @@ describe AutoMerge::BaseService do it 'does not yield block' do expect { |b| service.execute(merge_request, &b) }.not_to yield_control end + end + end + + describe '#cancel' do + subject { service.cancel(merge_request) } + + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + + it_behaves_like 'Canceled or Dropped' + + context 'when failed to save' do + before do + allow(merge_request).to receive(:save) { false } + end it 'returns error status' do expect(subject[:status]).to eq(:error) @@ -180,4 +190,24 @@ describe AutoMerge::BaseService do end end end + + describe '#abort' do + subject { service.abort(merge_request, reason) } + + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + let(:reason) { 'an error'} + + it_behaves_like 'Canceled or Dropped' + + context 'when failed to save' do + before do + allow(merge_request).to receive(:save) { false } + end + + it 'returns error status' do + expect(subject[:status]).to eq(:error) + expect(subject[:message]).to eq("Can't abort the automatic merge") + end + end + end end diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb index 5e84ef052ce..931b52470c4 100644 --- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb @@ -177,6 +177,17 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do end end + describe "#abort" do + before do + service.abort(mr_merge_if_green_enabled, 'an error') + end + + it 'posts a system note' do + note = mr_merge_if_green_enabled.notes.last + expect(note.note).to include 'aborted the automatic merge' + end + end + describe 'pipeline integration' do context 'when there are multiple stages in the pipeline' do let(:ref) { mr_merge_if_green_enabled.source_branch } diff --git a/spec/services/auto_merge_service_spec.rb b/spec/services/auto_merge_service_spec.rb index 93a22e60498..50dfc49a59c 100644 --- a/spec/services/auto_merge_service_spec.rb +++ b/spec/services/auto_merge_service_spec.rb @@ -161,4 +161,29 @@ describe AutoMergeService do end end end + + describe '#abort' do + subject { service.abort(merge_request, error) } + + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + let(:error) { 'an error' } + + it 'delegates to a relevant service instance' do + expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service| + expect(service).to receive(:abort).with(merge_request, error) + end + + subject + end + + context 'when auto merge is not enabled' do + let(:merge_request) { create(:merge_request) } + + it 'returns error' do + expect(subject[:message]).to eq("Can't abort the automatic merge") + expect(subject[:status]).to eq(:error) + expect(subject[:http_status]).to eq(406) + end + end + end end diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index 61f99f82a76..e2f201677fa 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -22,7 +22,6 @@ describe MergeRequests::MergeToRefService do shared_examples_for 'successfully merges to ref with merge method' do it 'writes commit to merge ref' do repository = project.repository - target_ref = merge_request.merge_ref_path expect(repository.ref_exists?(target_ref)).to be(false) @@ -33,7 +32,7 @@ describe MergeRequests::MergeToRefService do expect(result[:status]).to eq(:success) expect(result[:commit_id]).to be_present expect(result[:source_id]).to eq(merge_request.source_branch_sha) - expect(result[:target_id]).to eq(merge_request.target_branch_sha) + expect(result[:target_id]).to eq(repository.commit(first_parent_ref).sha) expect(repository.ref_exists?(target_ref)).to be(true) expect(ref_head.id).to eq(result[:commit_id]) end @@ -74,17 +73,22 @@ describe MergeRequests::MergeToRefService do describe '#execute' do let(:service) do - described_class.new(project, user, commit_message: 'Awesome message', - should_remove_source_branch: true) + described_class.new(project, user, **params) end + let(:params) { { commit_message: 'Awesome message', should_remove_source_branch: true } } + def process_merge_to_ref perform_enqueued_jobs do service.execute(merge_request) end end - it_behaves_like 'successfully merges to ref with merge method' + it_behaves_like 'successfully merges to ref with merge method' do + let(:first_parent_ref) { 'refs/heads/master' } + let(:target_ref) { merge_request.merge_ref_path } + end + it_behaves_like 'successfully evaluates pre-condition checks' context 'commit history comparison with regular MergeService' do @@ -129,14 +133,22 @@ describe MergeRequests::MergeToRefService do context 'when semi-linear merge method' do let(:merge_method) { :rebase_merge } - it_behaves_like 'successfully merges to ref with merge method' + it_behaves_like 'successfully merges to ref with merge method' do + let(:first_parent_ref) { 'refs/heads/master' } + let(:target_ref) { merge_request.merge_ref_path } + end + it_behaves_like 'successfully evaluates pre-condition checks' end context 'when fast-forward merge method' do let(:merge_method) { :ff } - it_behaves_like 'successfully merges to ref with merge method' + it_behaves_like 'successfully merges to ref with merge method' do + let(:first_parent_ref) { 'refs/heads/master' } + let(:target_ref) { merge_request.merge_ref_path } + end + it_behaves_like 'successfully evaluates pre-condition checks' end @@ -178,5 +190,34 @@ describe MergeRequests::MergeToRefService do it { expect(todo).not_to be_done } end + + describe 'cascading merge refs' do + set(:project) { create(:project, :repository) } + let(:params) { { commit_message: 'Cascading merge', first_parent_ref: first_parent_ref, target_ref: target_ref } } + + context 'when first merge happens' do + let(:merge_request) do + create(:merge_request, source_project: project, source_branch: 'feature', + target_project: project, target_branch: 'master') + end + + it_behaves_like 'successfully merges to ref with merge method' do + let(:first_parent_ref) { 'refs/heads/master' } + let(:target_ref) { 'refs/merge-requests/1/train' } + end + + context 'when second merge happens' do + let(:merge_request) do + create(:merge_request, source_project: project, source_branch: 'improve/awesome', + target_project: project, target_branch: 'master') + end + + it_behaves_like 'successfully merges to ref with merge method' do + let(:first_parent_ref) { 'refs/merge-requests/1/train' } + let(:target_ref) { 'refs/merge-requests/2/train' } + end + end + end + end end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 9f60e49290e..157cfc46e69 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -359,6 +359,22 @@ describe SystemNoteService do end end + describe '.abort_merge_when_pipeline_succeeds' do + let(:noteable) do + create(:merge_request, source_project: project, target_project: project) + end + + subject { described_class.abort_merge_when_pipeline_succeeds(noteable, project, author, 'merge request was closed') } + + it_behaves_like 'a system note' do + let(:action) { 'merge' } + end + + it "posts the 'merge when pipeline succeeds' system note" do + expect(subject.note).to eq "aborted the automatic merge because merge request was closed" + end + end + describe '.change_title' do let(:noteable) { create(:issue, project: project, title: 'Lorem ipsum') } @@ -1175,16 +1191,30 @@ describe SystemNoteService do end it 'links to the diff in the system note' do - expect(subject.note).to include('version 1') - diff_id = merge_request.merge_request_diff.id line_code = change_position.line_code(project.repository) - expect(subject.note).to include(diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: line_code)) + link = diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: line_code) + + expect(subject.note).to eq("changed this line in [version 1 of the diff](#{link})") + end + + context 'discussion is on an image' do + let(:discussion) { create(:image_diff_note_on_merge_request, project: project).to_discussion } + + it 'links to the diff in the system note' do + diff_id = merge_request.merge_request_diff.id + file_hash = change_position.file_hash + link = diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: file_hash) + + expect(subject.note).to eq("changed this file in [version 1 of the diff](#{link})") + end end end - context 'when the change_position is invalid for the discussion' do - let(:change_position) { project.commit(sample_commit.id) } + context 'when the change_position does not point to a valid version' do + before do + allow(merge_request).to receive(:version_params_for).and_return(nil) + end it 'creates a new note in the discussion' do # we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded. diff --git a/spec/support/helpers/position_tracer_helpers.rb b/spec/support/helpers/position_tracer_helpers.rb new file mode 100644 index 00000000000..bbf6e06dd40 --- /dev/null +++ b/spec/support/helpers/position_tracer_helpers.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module PositionTracerHelpers + def diff_refs(base_commit, head_commit) + Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id) + end + + def position(attrs = {}) + attrs.reverse_merge!( + diff_refs: old_diff_refs + ) + Gitlab::Diff::Position.new(attrs) + end + + def expect_new_position(attrs, result = subject) + aggregate_failures("expect new position #{attrs.inspect}") do + if attrs.nil? + expect(result[:outdated]).to be_truthy + else + new_position = result[:position] + + expect(result[:outdated]).to be_falsey + expect(new_position).not_to be_nil + expect(new_position.diff_refs).to eq(new_diff_refs) + + attrs.each do |attr, value| + expect(new_position.send(attr)).to eq(value) + end + end + end + end + + def expect_change_position(attrs, result = subject) + aggregate_failures("expect change position #{attrs.inspect}") do + change_position = result[:position] + + expect(result[:outdated]).to be_truthy + + if attrs.nil? || attrs.empty? + expect(change_position).to be_nil + else + expect(change_position).not_to be_nil + expect(change_position.diff_refs).to eq(change_diff_refs) + + attrs.each do |attr, value| + expect(change_position.send(attr)).to eq(value) + end + end + end + end + + def create_branch(new_name, branch_name) + CreateBranchService.new(project, current_user).execute(new_name, branch_name) + end + + def create_file(branch_name, file_name, content) + Files::CreateService.new( + project, + current_user, + start_branch: branch_name, + branch_name: branch_name, + commit_message: "Create file", + file_path: file_name, + file_content: content + ).execute + project.commit(branch_name) + end + + def update_file(branch_name, file_name, content) + Files::UpdateService.new( + project, + current_user, + start_branch: branch_name, + branch_name: branch_name, + commit_message: "Update file", + file_path: file_name, + file_content: content + ).execute + project.commit(branch_name) + end + + def delete_file(branch_name, file_name) + Files::DeleteService.new( + project, + current_user, + start_branch: branch_name, + branch_name: branch_name, + commit_message: "Delete file", + file_path: file_name + ).execute + project.commit(branch_name) + end +end |