diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2018-03-07 18:42:57 +0000 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2018-03-07 18:42:57 +0000 |
commit | 318aeffcd735486ff3bbb99325825788918373c7 (patch) | |
tree | 27a916e9bfa9a7f31ead5f5c53c6a366ea8730f9 /spec | |
parent | b9f9e6fa4e7a54d6caee81799ed88ee40dfb645b (diff) | |
parent | 67185097734bb88979df020123cf7327bc5d32c5 (diff) | |
download | gitlab-ce-318aeffcd735486ff3bbb99325825788918373c7.tar.gz |
[ci skip] Merge branch 'master' into 43770-change-clear-runners-cache-ujs-action-to-an-axios-request
* master: (68 commits)
Upgrade Workhorse to 4.0.0
naming things
Update GitLab Pages to v0.7.0
Minor fixes in API doc
Use Project#full_name instead of name_with_namespace
Fix tests not completely disabling Gitaly
Move OperationService#UserRemoveBranch
Move OperationService#UserCreateBranch
Move CommitService#Languages to OPT_OUT
Move RefService#CreateBranch to OPT_OUT
Move RefService#DeleteBranch to OPT_OUT
Move OperationService#UserRevert to OPT_OUT
Move OperationService#UserAddTag to OPT_OUT
Move CommitService::CommitPatch to OPT_OUT
Change to Pacific Time Zone
Merge branch 'pages-6-1-gitlab-10-5' into 'security-10-5'
Merge branch 'sh-fix-otp-backup-invalidation-10-5' into 'security-10-5'
Remove wrong assumption about Runners cache GC
Add CommonMark markdown engine
add nginx_status monitoring details
...
Diffstat (limited to 'spec')
20 files changed, 819 insertions, 545 deletions
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 00cf464ec5b..306094f7ffb 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -98,10 +98,8 @@ describe Projects::MilestonesController do it 'shows group milestone' do post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid - group_milestone = assigns(:milestone) - - expect(response).to redirect_to(group_milestone_path(project.group, group_milestone.iid)) - expect(flash[:notice]).to eq('Milestone has been promoted to group milestone.') + expect(flash[:notice]).to eq("#{milestone.title} promoted to group milestone") + expect(response).to redirect_to(project_milestones_path(project)) end end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 3f4e408b3a6..857333f222d 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -16,6 +16,8 @@ FactoryBot.define do factory :note_on_personal_snippet, traits: [:on_personal_snippet] factory :system_note, traits: [:system] + factory :discussion_note, class: DiscussionNote + factory :discussion_note_on_merge_request, traits: [:on_merge_request], class: DiscussionNote do association :project, :repository @@ -31,6 +33,8 @@ FactoryBot.define do factory :discussion_note_on_personal_snippet, traits: [:on_personal_snippet], class: DiscussionNote + factory :discussion_note_on_snippet, traits: [:on_snippet], class: DiscussionNote + factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote do @@ -96,6 +100,10 @@ FactoryBot.define do noteable { create(:issue, project: project) } end + trait :on_snippet do + noteable { create(:snippet, project: project) } + end + trait :on_merge_request do noteable { create(:merge_request, source_project: project) } end diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index f266f2ecc54..25ed3bdc88e 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -24,6 +24,16 @@ describe 'Admin::Hooks' do visit admin_hooks_path expect(page).to have_content(system_hook.url) end + + it 'renders plugins list as well' do + allow(Gitlab::Plugin).to receive(:files).and_return(['foo.rb', 'bar.clj']) + + visit admin_hooks_path + + expect(page).to have_content('Plugins') + expect(page).to have_content('foo.rb') + expect(page).to have_content('bar.clj') + end end describe 'New Hook' do diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 6ef235cf870..bc75dc5d19b 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -145,6 +145,18 @@ feature 'Login' do expect { enter_code(codes.sample) } .to change { user.reload.otp_backup_codes.size }.by(-1) end + + it 'invalidates backup codes twice in a row' do + random_code = codes.delete(codes.sample) + expect { enter_code(random_code) } + .to change { user.reload.otp_backup_codes.size }.by(-1) + + gitlab_sign_out + gitlab_sign_in(user) + + expect { enter_code(codes.sample) } + .to change { user.reload.otp_backup_codes.size }.by(-1) + end end context 'with invalid code' do diff --git a/spec/fixtures/api/schemas/public_api/v4/notes.json b/spec/fixtures/api/schemas/public_api/v4/notes.json index 6525f7c2c80..4c4ca3b582f 100644 --- a/spec/fixtures/api/schemas/public_api/v4/notes.json +++ b/spec/fixtures/api/schemas/public_api/v4/notes.json @@ -4,6 +4,7 @@ "type": "object", "properties" : { "id": { "type": "integer" }, + "type": { "type": ["string", "null"] }, "body": { "type": "string" }, "attachment": { "type": ["string", "null"] }, "author": { diff --git a/spec/javascripts/pages/labels/components/promote_label_modal_spec.js b/spec/javascripts/pages/labels/components/promote_label_modal_spec.js new file mode 100644 index 00000000000..ba2e07f02f7 --- /dev/null +++ b/spec/javascripts/pages/labels/components/promote_label_modal_spec.js @@ -0,0 +1,88 @@ +import Vue from 'vue'; +import promoteLabelModal from '~/pages/projects/labels/components/promote_label_modal.vue'; +import eventHub from '~/pages/projects/labels/event_hub'; +import axios from '~/lib/utils/axios_utils'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +describe('Promote label modal', () => { + let vm; + const Component = Vue.extend(promoteLabelModal); + const labelMockData = { + labelTitle: 'Documentation', + labelColor: '#5cb85c', + labelTextColor: '#ffffff', + url: `${gl.TEST_HOST}/dummy/promote/labels`, + }; + + describe('Modal title and description', () => { + beforeEach(() => { + vm = mountComponent(Component, labelMockData); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('contains the proper description', () => { + expect(vm.text).toContain('Promoting this label will make it available for all projects inside the group'); + }); + + it('contains a label span with the color', () => { + const labelFromTitle = vm.$el.querySelector('.modal-header .label.color-label'); + + expect(labelFromTitle.style.backgroundColor).not.toBe(null); + expect(labelFromTitle.textContent).toContain(vm.labelTitle); + }); + }); + + describe('When requesting a label promotion', () => { + beforeEach(() => { + vm = mountComponent(Component, { + ...labelMockData, + }); + spyOn(eventHub, '$emit'); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('redirects when a label is promoted', (done) => { + const responseURL = `${gl.TEST_HOST}/dummy/endpoint`; + spyOn(axios, 'post').and.callFake((url) => { + expect(url).toBe(labelMockData.url); + expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestStarted', labelMockData.url); + return Promise.resolve({ + request: { + responseURL, + }, + }); + }); + + vm.onSubmit() + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestFinished', { labelUrl: labelMockData.url, successful: true }); + }) + .then(done) + .catch(done.fail); + }); + + it('displays an error if promoting a label failed', (done) => { + const dummyError = new Error('promoting label failed'); + dummyError.response = { status: 500 }; + spyOn(axios, 'post').and.callFake((url) => { + expect(url).toBe(labelMockData.url); + expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestStarted', labelMockData.url); + return Promise.reject(dummyError); + }); + + vm.onSubmit() + .catch((error) => { + expect(error).toBe(dummyError); + expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestFinished', { labelUrl: labelMockData.url, successful: false }); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js b/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js new file mode 100644 index 00000000000..bf044fe8fb5 --- /dev/null +++ b/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js @@ -0,0 +1,83 @@ +import Vue from 'vue'; +import promoteMilestoneModal from '~/pages/milestones/shared/components/promote_milestone_modal.vue'; +import eventHub from '~/pages/milestones/shared/event_hub'; +import axios from '~/lib/utils/axios_utils'; +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +describe('Promote milestone modal', () => { + let vm; + const Component = Vue.extend(promoteMilestoneModal); + const milestoneMockData = { + milestoneTitle: 'v1.0', + url: `${gl.TEST_HOST}/dummy/promote/milestones`, + }; + + describe('Modal title and description', () => { + beforeEach(() => { + vm = mountComponent(Component, milestoneMockData); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('contains the proper description', () => { + expect(vm.text).toContain('Promoting this milestone will make it available for all projects inside the group.'); + }); + + it('contains the correct title', () => { + expect(vm.title).toEqual('Promote v1.0 to group milestone?'); + }); + }); + + describe('When requesting a milestone promotion', () => { + beforeEach(() => { + vm = mountComponent(Component, { + ...milestoneMockData, + }); + spyOn(eventHub, '$emit'); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('redirects when a milestone is promoted', (done) => { + const responseURL = `${gl.TEST_HOST}/dummy/endpoint`; + spyOn(axios, 'post').and.callFake((url) => { + expect(url).toBe(milestoneMockData.url); + expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestStarted', milestoneMockData.url); + return Promise.resolve({ + request: { + responseURL, + }, + }); + }); + + vm.onSubmit() + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestFinished', { milestoneUrl: milestoneMockData.url, successful: true }); + }) + .then(done) + .catch(done.fail); + }); + + it('displays an error if promoting a milestone failed', (done) => { + const dummyError = new Error('promoting milestone failed'); + dummyError.response = { status: 500 }; + spyOn(axios, 'post').and.callFake((url) => { + expect(url).toBe(milestoneMockData.url); + expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestStarted', milestoneMockData.url); + return Promise.reject(dummyError); + }); + + vm.onSubmit() + .catch((error) => { + expect(error).toBe(dummyError); + expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestFinished', { milestoneUrl: milestoneMockData.url, successful: false }); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js index d0fc10d69ea..f598b1afa74 100644 --- a/spec/javascripts/vue_shared/components/clipboard_button_spec.js +++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js @@ -10,6 +10,7 @@ describe('clipboard button', () => { vm = mountComponent(Component, { text: 'copy me', title: 'Copy this value into Clipboard!', + cssClass: 'btn-danger', }); }); @@ -28,4 +29,8 @@ describe('clipboard button', () => { expect(vm.$el.getAttribute('data-placement')).toEqual('top'); expect(vm.$el.getAttribute('data-container')).toEqual(null); }); + + it('should render provided classname', () => { + expect(vm.$el.classList).toContain('btn-danger'); + }); }); diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb index 167876ca158..2c63f3b0455 100644 --- a/spec/lib/gitlab/contributions_calendar_spec.rb +++ b/spec/lib/gitlab/contributions_calendar_spec.rb @@ -77,6 +77,13 @@ describe Gitlab::ContributionsCalendar do expect(calendar(contributor).activity_dates[today]).to eq(1) end + it "counts the discussions on merge requests and issues" do + create_event(public_project, today, 0, Event::COMMENTED, :discussion_note_on_merge_request) + create_event(public_project, today, 2, Event::COMMENTED, :discussion_note_on_issue) + + expect(calendar(contributor).activity_dates[today]).to eq(2) + end + context "when events fall under different dates depending on the time zone" do before do create_event(public_project, today, 1) diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index ceb570ac777..412eca4a56b 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -142,15 +142,15 @@ describe Environment do let(:commit) { project.commit.parent } it 'returns deployment id for the environment' do - expect(environment.first_deployment_for(commit)).to eq deployment1 + expect(environment.first_deployment_for(commit.id)).to eq deployment1 end it 'return nil when no deployment is found' do - expect(environment.first_deployment_for(head_commit)).to eq nil + expect(environment.first_deployment_for(head_commit.id)).to eq nil end it 'returns a UTF-8 ref' do - expect(environment.first_deployment_for(commit).ref).to be_utf8 + expect(environment.first_deployment_for(commit.id).ref).to be_utf8 end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 67f49348acb..8ea92410022 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -49,6 +49,22 @@ describe Event do end end end + + describe 'after_create :track_user_interacted_projects' do + let(:event) { build(:push_event, project: project, author: project.owner) } + + it 'passes event to UserInteractedProject.track' do + expect(UserInteractedProject).to receive(:available?).and_return(true) + expect(UserInteractedProject).to receive(:track).with(event) + event.save + end + + it 'does not call UserInteractedProject.track if its not yet available' do + expect(UserInteractedProject).to receive(:available?).and_return(false) + expect(UserInteractedProject).not_to receive(:track) + event.save + end + end end describe "Push event" do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 38653e18306..579069ffa14 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1004,7 +1004,7 @@ describe Repository do end end - context 'with Gitaly disabled', :skip_gitaly_mock do + context 'with Gitaly disabled', :disable_gitaly do context 'when pre hooks were successful' do it 'runs without errors' do hook = double(trigger: [true, nil]) @@ -1896,7 +1896,7 @@ describe Repository do it_behaves_like 'adding tag' end - context 'when Gitaly operation_user_add_tag feature is disabled', :skip_gitaly_mock do + context 'when Gitaly operation_user_add_tag feature is disabled', :disable_gitaly do it_behaves_like 'adding tag' it 'passes commit SHA to pre-receive and update hooks and tag SHA to post-receive hook' do @@ -1955,7 +1955,7 @@ describe Repository do end end - context 'with gitaly disabled', :skip_gitaly_mock do + context 'with gitaly disabled', :disable_gitaly do it_behaves_like "user deleting a branch" let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb new file mode 100644 index 00000000000..cb4bb3372d4 --- /dev/null +++ b/spec/models/user_interacted_project_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe UserInteractedProject do + describe '.track' do + subject { described_class.track(event) } + let(:event) { build(:event) } + + Event::ACTIONS.each do |action| + context "for all actions (event types)" do + let(:event) { build(:event, action: action) } + it 'creates a record' do + expect { subject }.to change { described_class.count }.from(0).to(1) + end + end + end + + it 'sets project accordingly' do + subject + expect(described_class.first.project).to eq(event.project) + end + + it 'sets user accordingly' do + subject + expect(described_class.first.user).to eq(event.author) + end + + it 'only creates a record once per user/project' do + expect do + subject + described_class.track(event) + end.to change { described_class.count }.from(0).to(1) + end + + describe 'with an event without a project' do + let(:event) { build(:event, project: nil) } + + it 'ignores the event' do + expect { subject }.not_to change { described_class.count } + end + end + end + + describe '.available?' do + before do + described_class.instance_variable_set('@available_flag', nil) + end + + it 'checks schema version and properly caches positive result' do + expect(ActiveRecord::Migrator).to receive(:current_version).and_return(described_class::REQUIRED_SCHEMA_VERSION - 1 - rand(1000)) + expect(described_class.available?).to be_falsey + expect(ActiveRecord::Migrator).to receive(:current_version).and_return(described_class::REQUIRED_SCHEMA_VERSION + rand(1000)) + expect(described_class.available?).to be_truthy + expect(ActiveRecord::Migrator).not_to receive(:current_version) + expect(described_class.available?).to be_truthy # cached response + end + end + + it { is_expected.to validate_presence_of(:project_id) } + it { is_expected.to validate_presence_of(:user_id) } +end diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb new file mode 100644 index 00000000000..4a44b219a67 --- /dev/null +++ b/spec/requests/api/discussions_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe API::Discussions do + let(:user) { create(:user) } + let!(:project) { create(:project, :public, namespace: user.namespace) } + let(:private_user) { create(:user) } + + before do + project.add_reporter(user) + end + + context "when noteable is an Issue" do + let!(:issue) { create(:issue, project: project, author: user) } + let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) } + + it_behaves_like "discussions API", 'projects', 'issues', 'iid' do + let(:parent) { project } + let(:noteable) { issue } + let(:note) { issue_note } + end + end + + context "when noteable is a Snippet" do + let!(:snippet) { create(:project_snippet, project: project, author: user) } + let!(:snippet_note) { create(:discussion_note_on_snippet, noteable: snippet, project: project, author: user) } + + it_behaves_like "discussions API", 'projects', 'snippets', 'id' do + let(:parent) { project } + let(:noteable) { snippet } + let(:note) { snippet_note } + end + end +end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 827f4c04324..ca0aac87ba9 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -335,21 +335,8 @@ describe API::Internal do end context "git push" do - context "gitaly disabled", :disable_gitaly do - it "has the correct payload" do - push(key, project) - - expect(response).to have_gitlab_http_status(200) - expect(json_response["status"]).to be_truthy - expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(json_response["gl_repository"]).to eq("project-#{project.id}") - expect(json_response["gitaly"]).to be_nil - expect(user).not_to have_an_activity_record - end - end - - context "gitaly enabled" do - it "has the correct payload" do + context 'project as namespace/project' do + it do push(key, project) expect(response).to have_gitlab_http_status(200) @@ -365,17 +352,6 @@ describe API::Internal do expect(user).not_to have_an_activity_record end end - - context 'project as namespace/project' do - it do - push(key, project) - - expect(response).to have_gitlab_http_status(200) - expect(json_response["status"]).to be_truthy - expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(json_response["gl_repository"]).to eq("project-#{project.id}") - end - end end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 981c9c27325..dd568c24c72 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -3,117 +3,86 @@ require 'spec_helper' describe API::Notes do let(:user) { create(:user) } let!(:project) { create(:project, :public, namespace: user.namespace) } - let!(:issue) { create(:issue, project: project, author: user) } - let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } - let!(:snippet) { create(:project_snippet, project: project, author: user) } - let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } - let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) } - let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) } - - # For testing the cross-reference of a private issue in a public issue let(:private_user) { create(:user) } - let(:private_project) do - create(:project, namespace: private_user.namespace) - .tap { |p| p.add_master(private_user) } - end - let(:private_issue) { create(:issue, project: private_project) } - - let(:ext_proj) { create(:project, :public) } - let(:ext_issue) { create(:issue, project: ext_proj) } - - let!(:cross_reference_note) do - create :note, - noteable: ext_issue, project: ext_proj, - note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", - system: true - end before do project.add_reporter(user) end - describe "GET /projects/:id/noteable/:noteable_id/notes" do - context "when noteable is an Issue" do - context 'sorting' do - before do - create_list(:note, 3, noteable: issue, project: project, author: user) - end - - it 'sorts by created_at in descending order by default' do - get api("/projects/#{project.id}/issues/#{issue.iid}/notes", user) - - response_dates = json_response.map { |noteable| noteable['created_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts by ascending order when requested' do - get api("/projects/#{project.id}/issues/#{issue.iid}/notes?sort=asc", user) - - response_dates = json_response.map { |noteable| noteable['created_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort) - end - - it 'sorts by updated_at in descending order when requested' do - get api("/projects/#{project.id}/issues/#{issue.iid}/notes?order_by=updated_at", user) - - response_dates = json_response.map { |noteable| noteable['updated_at'] } + context "when noteable is an Issue" do + let!(:issue) { create(:issue, project: project, author: user) } + let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort.reverse) - end + it_behaves_like "noteable API", 'projects', 'issues', 'iid' do + let(:parent) { project } + let(:noteable) { issue } + let(:note) { issue_note } + end - it 'sorts by updated_at in ascending order when requested' do - get api("/projects/#{project.id}/issues/#{issue.iid}/notes??order_by=updated_at&sort=asc", user) + context 'when user does not have access to create noteable' do + let(:private_issue) { create(:issue, project: create(:project, :private)) } - response_dates = json_response.map { |noteable| noteable['updated_at'] } + ## + # We are posting to project user has access to, but we use issue id + # from a different project, see #15577 + # + before do + post api("/projects/#{private_issue.project.id}/issues/#{private_issue.iid}/notes", user), + body: 'Hi!' + end - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort) - end + it 'responds with resource not found error' do + expect(response.status).to eq 404 end - it "returns an array of issue notes" do - get api("/projects/#{project.id}/issues/#{issue.iid}/notes", user) + it 'does not create new note' do + expect(private_issue.notes.reload).to be_empty + end + end - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['body']).to eq(issue_note.note) + context "when referencing other project" do + # For testing the cross-reference of a private issue in a public project + let(:private_project) do + create(:project, namespace: private_user.namespace) + .tap { |p| p.add_master(private_user) } end + let(:private_issue) { create(:issue, project: private_project) } - it "returns a 404 error when issue id not found" do - get api("/projects/#{project.id}/issues/12345/notes", user) + let(:ext_proj) { create(:project, :public) } + let(:ext_issue) { create(:issue, project: ext_proj) } - expect(response).to have_gitlab_http_status(404) + let!(:cross_reference_note) do + create :note, + noteable: ext_issue, project: ext_proj, + note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", + system: true end - context "and current user cannot view the notes" do - it "returns an empty array" do - get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response).to be_empty - end + describe "GET /projects/:id/noteable/:noteable_id/notes" do + context "current user cannot view the notes" do + it "returns an empty array" do + get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user) - context "and issue is confidential" do - before do - ext_issue.update_attributes(confidential: true) + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response).to be_empty end - it "returns 404" do - get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user) + context "issue is confidential" do + before do + ext_issue.update_attributes(confidential: true) + end - expect(response).to have_gitlab_http_status(404) + it "returns 404" do + get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user) + + expect(response).to have_gitlab_http_status(404) + end end end - context "and current user can view the note" do + context "current user can view the note" do it "returns an empty array" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", private_user) @@ -124,172 +93,29 @@ describe API::Notes do end end end - end - - context "when noteable is a Snippet" do - context 'sorting' do - before do - create_list(:note, 3, noteable: snippet, project: project, author: user) - end - - it 'sorts by created_at in descending order by default' do - get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - - response_dates = json_response.map { |noteable| noteable['created_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts by ascending order when requested' do - get api("/projects/#{project.id}/snippets/#{snippet.id}/notes?sort=asc", user) - - response_dates = json_response.map { |noteable| noteable['created_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort) - end - - it 'sorts by updated_at in descending order when requested' do - get api("/projects/#{project.id}/snippets/#{snippet.id}/notes?order_by=updated_at", user) - - response_dates = json_response.map { |noteable| noteable['updated_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort.reverse) - end - it 'sorts by updated_at in ascending order when requested' do - get api("/projects/#{project.id}/snippets/#{snippet.id}/notes??order_by=updated_at&sort=asc", user) + describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do + context "current user cannot view the notes" do + it "returns a 404 error" do + get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes/#{cross_reference_note.id}", user) - response_dates = json_response.map { |noteable| noteable['updated_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort) - end - end - it "returns an array of snippet notes" do - get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['body']).to eq(snippet_note.note) - end - - it "returns a 404 error when snippet id not found" do - get api("/projects/#{project.id}/snippets/42/notes", user) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 when not authorized" do - get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user) - - expect(response).to have_gitlab_http_status(404) - end - end - - context "when noteable is a Merge Request" do - context 'sorting' do - before do - create_list(:note, 3, noteable: merge_request, project: project, author: user) - end - - it 'sorts by created_at in descending order by default' do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user) - - response_dates = json_response.map { |noteable| noteable['created_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts by ascending order when requested' do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes?sort=asc", user) - - response_dates = json_response.map { |noteable| noteable['created_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort) - end - - it 'sorts by updated_at in descending order when requested' do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes?order_by=updated_at", user) - - response_dates = json_response.map { |noteable| noteable['updated_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort.reverse) - end - - it 'sorts by updated_at in ascending order when requested' do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes??order_by=updated_at&sort=asc", user) - - response_dates = json_response.map { |noteable| noteable['updated_at'] } - - expect(json_response.length).to eq(4) - expect(response_dates).to eq(response_dates.sort) - end - end - it "returns an array of merge_requests notes" do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['body']).to eq(merge_request_note.note) - end - - it "returns a 404 error if merge request id not found" do - get api("/projects/#{project.id}/merge_requests/4444/notes", user) - - expect(response).to have_gitlab_http_status(404) - end - - it "returns 404 when not authorized" do - get api("/projects/#{project.id}/merge_requests/4444/notes", private_user) - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do - context "when noteable is an Issue" do - it "returns an issue note by id" do - get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq(issue_note.note) - end - - it "returns a 404 error if issue note not found" do - get api("/projects/#{project.id}/issues/#{issue.iid}/notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - - context "and current user cannot view the note" do - it "returns a 404 error" do - get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes/#{cross_reference_note.id}", user) - - expect(response).to have_gitlab_http_status(404) - end - - context "when issue is confidential" do - before do - issue.update_attributes(confidential: true) + expect(response).to have_gitlab_http_status(404) end - it "returns 404" do - get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", private_user) + context "when issue is confidential" do + before do + issue.update_attributes(confidential: true) + end - expect(response).to have_gitlab_http_status(404) + it "returns 404" do + get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", private_user) + + expect(response).to have_gitlab_http_status(404) + end end end - context "and current user can view the note" do + context "current user can view the note" do it "returns an issue note by id" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes/#{cross_reference_note.id}", private_user) @@ -299,132 +125,27 @@ describe API::Notes do end end end - - context "when noteable is a Snippet" do - it "returns a snippet note by id" do - get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq(snippet_note.note) - end - - it "returns a 404 error if snippet note not found" do - get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - end end - describe "POST /projects/:id/noteable/:noteable_id/notes" do - context "when noteable is an Issue" do - it "creates a new issue note" do - post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: 'hi!' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq('hi!') - expect(json_response['author']['username']).to eq(user.username) - end - - it "returns a 400 bad request error if body not given" do - post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user) - - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 401 unauthorized error if user not authenticated" do - post api("/projects/#{project.id}/issues/#{issue.iid}/notes"), body: 'hi!' - - expect(response).to have_gitlab_http_status(401) - end - - context 'when an admin or owner makes the request' do - it 'accepts the creation date to be set' do - creation_time = 2.weeks.ago - post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), - body: 'hi!', created_at: creation_time - - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq('hi!') - expect(json_response['author']['username']).to eq(user.username) - expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) - end - end - - context 'when the user is posting an award emoji on an issue created by someone else' do - let(:issue2) { create(:issue, project: project) } - - it 'creates a new issue note' do - post api("/projects/#{project.id}/issues/#{issue2.iid}/notes", user), body: ':+1:' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq(':+1:') - end - end - - context 'when the user is posting an award emoji on his/her own issue' do - it 'creates a new issue note' do - post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: ':+1:' - - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq(':+1:') - end - end - end - - context "when noteable is a Snippet" do - it "creates a new snippet note" do - post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!' + context "when noteable is a Snippet" do + let!(:snippet) { create(:project_snippet, project: project, author: user) } + let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) } - expect(response).to have_gitlab_http_status(201) - expect(json_response['body']).to eq('hi!') - expect(json_response['author']['username']).to eq(user.username) - end - - it "returns a 400 bad request error if body not given" do - post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - - expect(response).to have_gitlab_http_status(400) - end - - it "returns a 401 unauthorized error if user not authenticated" do - post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!' - - expect(response).to have_gitlab_http_status(401) - end + it_behaves_like "noteable API", 'projects', 'snippets', 'id' do + let(:parent) { project } + let(:noteable) { snippet } + let(:note) { snippet_note } end + end - context 'when user does not have access to read the noteable' do - it 'responds with 404' do - project = create(:project, :private) { |p| p.add_guest(user) } - issue = create(:issue, :confidential, project: project) - - post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), - body: 'Foo' - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when user does not have access to create noteable' do - let(:private_issue) { create(:issue, project: create(:project, :private)) } - - ## - # We are posting to project user has access to, but we use issue id - # from a different project, see #15577 - # - before do - post api("/projects/#{private_issue.project.id}/issues/#{private_issue.iid}/notes", user), - body: 'Hi!' - end - - it 'responds with resource not found error' do - expect(response.status).to eq 404 - end + context "when noteable is a Merge Request" do + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } + let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) } - it 'does not create new note' do - expect(private_issue.notes.reload).to be_empty - end + it_behaves_like "noteable API", 'projects', 'merge_requests', 'iid' do + let(:parent) { project } + let(:noteable) { merge_request } + let(:note) { merge_request_note } end context 'when the merge request discussion is locked' do @@ -461,145 +182,4 @@ describe API::Notes do end end end - - describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do - it "creates an activity event when an issue note is created" do - expect(Event).to receive(:create!) - - post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: 'hi!' - end - end - - describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do - context 'when noteable is an Issue' do - it 'returns modified note' do - put api("/projects/#{project.id}/issues/#{issue.iid}/"\ - "notes/#{issue_note.id}", user), body: 'Hello!' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq('Hello!') - end - - it 'returns a 404 error when note id not found' do - put api("/projects/#{project.id}/issues/#{issue.iid}/notes/12345", user), - body: 'Hello!' - - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 400 bad request error if body not given' do - put api("/projects/#{project.id}/issues/#{issue.iid}/"\ - "notes/#{issue_note.id}", user) - - expect(response).to have_gitlab_http_status(400) - end - end - - context 'when noteable is a Snippet' do - it 'returns modified note' do - put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/#{snippet_note.id}", user), body: 'Hello!' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq('Hello!') - end - - it 'returns a 404 error when note id not found' do - put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/12345", user), body: "Hello!" - - expect(response).to have_gitlab_http_status(404) - end - end - - context 'when noteable is a Merge Request' do - it 'returns modified note' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/"\ - "notes/#{merge_request_note.id}", user), body: 'Hello!' - - expect(response).to have_gitlab_http_status(200) - expect(json_response['body']).to eq('Hello!') - end - - it 'returns a 404 error when note id not found' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/"\ - "notes/12345", user), body: "Hello!" - - expect(response).to have_gitlab_http_status(404) - end - end - end - - describe 'DELETE /projects/:id/noteable/:noteable_id/notes/:note_id' do - context 'when noteable is an Issue' do - it 'deletes a note' do - delete api("/projects/#{project.id}/issues/#{issue.iid}/"\ - "notes/#{issue_note.id}", user) - - expect(response).to have_gitlab_http_status(204) - # Check if note is really deleted - delete api("/projects/#{project.id}/issues/#{issue.iid}/"\ - "notes/#{issue_note.id}", user) - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 404 error when note id not found' do - delete api("/projects/#{project.id}/issues/#{issue.iid}/notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - - it_behaves_like '412 response' do - let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", user) } - end - end - - context 'when noteable is a Snippet' do - it 'deletes a note' do - delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/#{snippet_note.id}", user) - - expect(response).to have_gitlab_http_status(204) - # Check if note is really deleted - delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/#{snippet_note.id}", user) - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 404 error when note id not found' do - delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ - "notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - - it_behaves_like '412 response' do - let(:request) { api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) } - end - end - - context 'when noteable is a Merge Request' do - it 'deletes a note' do - delete api("/projects/#{project.id}/merge_requests/"\ - "#{merge_request.iid}/notes/#{merge_request_note.id}", user) - - expect(response).to have_gitlab_http_status(204) - # Check if note is really deleted - delete api("/projects/#{project.id}/merge_requests/"\ - "#{merge_request.iid}/notes/#{merge_request_note.id}", user) - expect(response).to have_gitlab_http_status(404) - end - - it 'returns a 404 error when note id not found' do - delete api("/projects/#{project.id}/merge_requests/"\ - "#{merge_request.iid}/notes/12345", user) - - expect(response).to have_gitlab_http_status(404) - end - - it_behaves_like '412 response' do - let(:request) { api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes/#{merge_request_note.id}", user) } - end - end - end end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 80a271ba7fb..d2072198d83 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -147,9 +147,9 @@ describe MergeRequestWidgetEntity do allow(resource).to receive(:diff_head_sha) { 'sha' } end - context 'when no diff head commit' do + context 'when diff head commit is empty' do it 'returns nil' do - allow(resource).to receive(:diff_head_commit) { nil } + allow(resource).to receive(:diff_head_sha) { '' } expect(subject[:diff_head_sha]).to be_nil end @@ -157,8 +157,6 @@ describe MergeRequestWidgetEntity do context 'when diff head commit present' do it 'returns diff head commit short id' do - allow(resource).to receive(:diff_head_commit) { double } - expect(subject[:diff_head_sha]).to eq('sha') end end diff --git a/spec/support/matchers/match_ids.rb b/spec/support/matchers/match_ids.rb new file mode 100644 index 00000000000..d8424405b96 --- /dev/null +++ b/spec/support/matchers/match_ids.rb @@ -0,0 +1,24 @@ +RSpec::Matchers.define :match_ids do |*expected| + match do |actual| + actual_ids = map_ids(actual) + expected_ids = map_ids(expected) + + expect(actual_ids).to match_array(expected_ids) + end + + description do + 'matches elements by ids' + end + + def map_ids(elements) + elements = elements.flatten if elements.respond_to?(:flatten) + + if elements.respond_to?(:map) + elements.map(&:id) + elsif elements.respond_to?(:id) + [elements.id] + else + raise ArgumentError, "could not map elements to ids: #{elements}" + end + end +end diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb new file mode 100644 index 00000000000..b6aeb30d69c --- /dev/null +++ b/spec/support/shared_examples/requests/api/discussions.rb @@ -0,0 +1,169 @@ +shared_examples 'discussions API' do |parent_type, noteable_type, id_name| + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do + it "returns an array of discussions" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(note.discussion_id) + end + + it "returns a 404 error when noteable id not found" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/12345/discussions", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", private_user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do + it "returns a discussion by id" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/#{note.discussion_id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(note.discussion_id) + expect(json_response['notes'].first['body']).to eq(note.note) + end + + it "returns a 404 error if discussion not found" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do + it "creates a new note" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['notes'].first['body']).to eq('hi!') + expect(json_response['notes'].first['author']['username']).to eq(user.username) + end + + it "returns a 400 bad request error if body not given" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 401 unauthorized error if user not authenticated" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), body: 'hi!' + + expect(response).to have_gitlab_http_status(401) + end + + context 'when an admin or owner makes the request' do + it 'accepts the creation date to be set' do + creation_time = 2.weeks.ago + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), + body: 'hi!', created_at: creation_time + + expect(response).to have_gitlab_http_status(201) + expect(json_response['notes'].first['body']).to eq('hi!') + expect(json_response['notes'].first['author']['username']).to eq(user.username) + expect(Time.parse(json_response['notes'].first['created_at'])).to be_like_time(creation_time) + end + end + + context 'when user does not have access to read the discussion' do + before do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'responds with 404' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", private_user), + body: 'Foo' + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do + it 'adds a new note to the discussion' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes", user), body: 'Hello!' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq('Hello!') + expect(json_response['type']).to eq('DiscussionNote') + end + + it 'returns a 400 bad request error if body not given' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 400 bad request error if discussion is individual note" do + note.update_attribute(:type, nil) + + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes", user), body: 'hi!' + + expect(response).to have_gitlab_http_status(400) + end + end + + describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + it 'returns modified note' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user), body: 'Hello!' + + expect(response).to have_gitlab_http_status(200) + expect(json_response['body']).to eq('Hello!') + end + + it 'returns a 404 error when note id not found' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/12345", user), + body: 'Hello!' + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 400 bad request error if body not given' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(400) + end + end + + describe "DELETE /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + it 'deletes a note' do + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(204) + # Check if note is really deleted + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 404 error when note id not found' do + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + + it_behaves_like '412 response' do + let(:request) do + api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb new file mode 100644 index 00000000000..79b2196660c --- /dev/null +++ b/spec/support/shared_examples/requests/api/notes.rb @@ -0,0 +1,206 @@ +shared_examples 'noteable API' do |parent_type, noteable_type, id_name| + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do + context 'sorting' do + before do + params = { noteable: noteable, author: user } + params[:project] = parent if parent.is_a?(Project) + + create_list(:note, 3, params) + end + + it 'sorts by created_at in descending order by default' do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + + response_dates = json_response.map { |note| note['created_at'] } + + expect(json_response.length).to eq(4) + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by ascending order when requested' do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?sort=asc", user) + + response_dates = json_response.map { |note| note['created_at'] } + + expect(json_response.length).to eq(4) + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at in descending order when requested' do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at", user) + + response_dates = json_response.map { |note| note['updated_at'] } + + expect(json_response.length).to eq(4) + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at in ascending order when requested' do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at&sort=asc", user) + + response_dates = json_response.map { |note| note['updated_at'] } + + expect(json_response.length).to eq(4) + expect(response_dates).to eq(response_dates.sort) + end + end + + it "returns an array of notes" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['body']).to eq(note.note) + end + + it "returns a 404 error when noteable id not found" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/12345/notes", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do + it "returns a note by id" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['body']).to eq(note.note) + end + + it "returns a 404 error if note not found" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do + it "creates a new note" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['author']['username']).to eq(user.username) + end + + it "returns a 400 bad request error if body not given" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 401 unauthorized error if user not authenticated" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes"), body: 'hi!' + + expect(response).to have_gitlab_http_status(401) + end + + it "creates an activity event when a note is created" do + expect(Event).to receive(:create!) + + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!' + end + + context 'when an admin or owner makes the request' do + it 'accepts the creation date to be set' do + creation_time = 2.weeks.ago + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), + body: 'hi!', created_at: creation_time + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['author']['username']).to eq(user.username) + expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) + end + end + + context 'when the user is posting an award emoji on a noteable created by someone else' do + it 'creates a new note' do + parent.add_developer(private_user) + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), body: ':+1:' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq(':+1:') + end + end + + context 'when the user is posting an award emoji on his/her own noteable' do + it 'creates a new note' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: ':+1:' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq(':+1:') + end + end + + context 'when user does not have access to read the noteable' do + before do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'responds with 404' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), + body: 'Foo' + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do + it 'returns modified note' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "notes/#{note.id}", user), body: 'Hello!' + + expect(response).to have_gitlab_http_status(200) + expect(json_response['body']).to eq('Hello!') + end + + it 'returns a 404 error when note id not found' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user), + body: 'Hello!' + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 400 bad request error if body not given' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(400) + end + end + + describe "DELETE /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do + it 'deletes a note' do + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(204) + # Check if note is really deleted + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "notes/#{note.id}", user) + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 404 error when note id not found' do + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + + it_behaves_like '412 response' do + let(:request) { api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user) } + end + end +end |