diff options
Diffstat (limited to 'spec')
90 files changed, 1397 insertions, 221 deletions
diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb index a3ce08f736c..3bf0ec799c7 100644 --- a/spec/controllers/concerns/send_file_upload_spec.rb +++ b/spec/controllers/concerns/send_file_upload_spec.rb @@ -115,7 +115,7 @@ describe SendFileUpload do it 'sends a file with a custom type' do headers = double - expected_headers = /response-content-disposition=attachment%3B%20filename%3D%22test.js%22%3B%20filename%2A%3DUTF-8%27%27test.js&response-content-type=application%2Fecmascript/ + expected_headers = /response-content-disposition=attachment%3B%20filename%3D%22test.js%22%3B%20filename%2A%3DUTF-8%27%27test.js&response-content-type=application%2Fjavascript/ expect(Gitlab::Workhorse).to receive(:send_url).with(expected_headers).and_call_original expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-url:/) diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 40c3a6d90d0..33254d607c9 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -26,6 +26,7 @@ describe 'Database schema' do forked_project_links: %w[forked_from_project_id], identities: %w[user_id], issues: %w[last_edited_by_id state_id], + jira_tracker_data: %w[jira_issue_transition_id], keys: %w[user_id], label_links: %w[target_id], lfs_objects_projects: %w[lfs_object_id project_id], diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb index c225387a5de..4804d0bb884 100644 --- a/spec/factories/lfs_objects_projects.rb +++ b/spec/factories/lfs_objects_projects.rb @@ -2,5 +2,6 @@ FactoryBot.define do factory :lfs_objects_project do lfs_object project + repository_type :project end end diff --git a/spec/factories/services_data.rb b/spec/factories/services_data.rb new file mode 100644 index 00000000000..387e130a743 --- /dev/null +++ b/spec/factories/services_data.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :jira_tracker_data do + service + url 'http://jira.example.com' + api_url 'http://api-jira.example.com' + username 'jira_username' + password 'jira_password' + end + + factory :issue_tracker_data do + service + project_url 'http://issuetracker.example.com' + issues_url 'http://issues.example.com' + new_issue_url 'http://new-issue.example.com' + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 1d2b724a5e5..4f3392cdcbf 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -66,6 +66,16 @@ FactoryBot.define do end end + transient do + developer_projects [] + end + + after(:create) do |user, evaluator| + evaluator.developer_projects.each do |project| + project.add_developer(user) + end + end + factory :omniauth_user do transient do extern_uid '123456' diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index e6f44aa7d20..a2dd34e7f7c 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -10,8 +10,7 @@ describe 'Commits' do stub_ci_pipeline_to_return_yaml_file end - let(:creator) { create(:user) } - + let(:creator) { create(:user, developer_projects: [project]) } let!(:pipeline) do create(:ci_pipeline, project: project, @@ -77,10 +76,11 @@ describe 'Commits' do describe 'Commit builds', :js do before do + project.add_developer(user) visit pipeline_path(pipeline) end - it 'shows pipeline`s data' do + it 'shows pipeline data' do expect(page).to have_content pipeline.sha[0..7] expect(page).to have_content pipeline.git_commit_message.gsub!(/\s+/, ' ') expect(page).to have_content pipeline.user.name diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb index 48edc764a8e..4108a0f370d 100644 --- a/spec/features/cycle_analytics_spec.rb +++ b/spec/features/cycle_analytics_spec.rb @@ -58,7 +58,7 @@ describe 'Cycle Analytics', :js do expect_issue_to_be_present click_stage('Plan') - expect(find('.stage-events')).to have_content(mr.commits.last.title) + expect_issue_to_be_present click_stage('Code') expect_merge_request_to_be_present diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index e75c43d5338..fb76e2b0014 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -125,7 +125,7 @@ describe 'Dashboard Groups page', :js do end it 'loads results for next page' do - expect(page).to have_selector('.gl-pagination .page-item a[role=menuitemradio]', count: 2) + expect(page).to have_selector('.gl-pagination .page-item a.page-link', count: 3) # Check first page expect(page).to have_content(group2.full_name) @@ -134,7 +134,7 @@ describe 'Dashboard Groups page', :js do expect(page).not_to have_selector("#group-#{group.id}") # Go to next page - find('.gl-pagination .page-item:not(.active) a[role=menuitemradio]').click + find('.gl-pagination .page-item:last-of-type a.page-link').click wait_for_requests diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb index 042ecdb172a..94bd7c53bb5 100644 --- a/spec/features/issues/markdown_toolbar_spec.rb +++ b/spec/features/issues/markdown_toolbar_spec.rb @@ -32,7 +32,7 @@ describe 'Issue markdown toolbar', :js do find('.js-main-target-form #note-body') page.evaluate_script('document.querySelectorAll(".js-main-target-form #note-body")[0].setSelectionRange(4, 50)') - find('.toolbar-btn:nth-child(2)').click + all('.toolbar-btn')[1].click expect(find('#note-body')[:value]).to eq("test\n*underline*\n") end diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 0f604db870f..2789d574156 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -92,6 +92,19 @@ describe "User creates issue" do .and have_content(label_titles.first) end end + + context "with Zoom link" do + it "adds Zoom button" do + issue_title = "Issue containing Zoom meeting link" + zoom_url = "https://gitlab.zoom.us/j/123456789" + + fill_in("Title", with: issue_title) + fill_in("Description", with: zoom_url) + click_button("Submit issue") + + expect(page).to have_link('Join Zoom meeting', href: zoom_url) + end + end end context "when signed in as user with special characters in their name" do diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 586b3ba170d..85c4d778fd0 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -52,7 +52,7 @@ describe 'Merge request > User merges when pipeline succeeds', :js do # so we have to wait for asynchronous call to reload it # and have_content expectation handles that. # - expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) running" + expect(page).to have_content "Pipeline ##{pipeline.id} running" end it_behaves_like 'Merge when pipeline succeeds activator' diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index e5770905dbd..8ff24449b39 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -141,7 +141,7 @@ describe 'Merge request > User posts notes', :js do page.within('.current-note-edit-form') do expect(find('#note_note').value).to eq('This is the new content') - find('.js-md:first-child').click + first('.js-md').click expect(find('#note_note').value).to eq('This is the new content****') end end diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 393077a916f..733e8aa3eba 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -160,7 +160,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{pipeline.ref}") end @@ -189,7 +189,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch}") @@ -201,7 +201,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch}") @@ -234,7 +234,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch} " \ @@ -248,7 +248,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch} " \ diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb index 780de76d2c5..e8b4fc8f160 100644 --- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -28,6 +28,18 @@ describe 'User comments on a diff', :js do end context 'single suggestion note' do + it 'hides suggestion popover' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + expect(page).to have_selector('.diff-suggest-popover') + + page.within('.diff-suggest-popover') do + click_button 'Got it' + end + + expect(page).not_to have_selector('.diff-suggest-popover') + end + it 'suggestion is presented' do click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index a84fee34669..fc74a370e72 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -61,7 +61,7 @@ describe 'User browses commits' do it 'renders commit ci info' do visit project_commit_path(project, sample_commit.id) - expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) pending" + expect(page).to have_content "Pipeline ##{pipeline.id} pending" end end diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb index bd6c73f4b85..ebc20d15d67 100644 --- a/spec/features/projects/jobs/user_browses_jobs_spec.rb +++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb @@ -16,12 +16,6 @@ describe 'User browses jobs' do visit(project_jobs_path(project)) end - it 'shows pipeline id and IID' do - page.within('td.pipeline-link') do - expect(page).to have_content("##{pipeline.id} (##{pipeline.iid})") - end - end - it 'shows the coverage' do page.within('td.coverage') do expect(page).to have_content('99.9%') diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 03562bd382e..f4ed89adc0f 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -129,7 +129,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) within '.js-pipeline-info' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) for #{pipeline.ref}") + expect(page).to have_content("Pipeline ##{pipeline.id} for #{pipeline.ref}") end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 77f0f237d0a..9759fd04ad2 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe 'Pipeline', :js do include RoutesHelpers include ProjectForksHelper + include ::ExclusiveLeaseHelpers let(:project) { create(:project) } let(:user) { create(:user) } @@ -539,6 +540,44 @@ describe 'Pipeline', :js do expect(page).to have_selector('.pipeline-visualization') expect(page).to have_content('cross-build') end + + context 'when a scheduled pipeline is created by a blocked user' do + let(:project) { create(:project, :repository) } + + let(:schedule) do + create(:ci_pipeline_schedule, + project: project, + owner: project.owner, + description: 'blocked user schedule' + ).tap do |schedule| + schedule.update_column(:next_run_at, 1.minute.ago) + end + end + + before do + schedule.owner.block! + + begin + PipelineScheduleWorker.new.perform + rescue Ci::CreatePipelineService::CreateError + # Do nothing, assert view code after the Pipeline failed to create. + end + end + + it 'displays the PipelineSchedule in an active state' do + visit project_pipeline_schedules_path(project) + page.click_link('Active') + + expect(page).to have_selector('table.ci-table > tbody > tr > td', text: 'blocked user schedule') + end + + it 'does not create a new Pipeline' do + visit project_pipelines_path(project) + + expect(page).not_to have_selector('.ci-table') + expect(schedule.last_pipeline).to be_nil + end + end end describe 'GET /:project/pipelines/:id/builds' do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index de780f13681..885d5f85989 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -469,7 +469,7 @@ describe 'Pipelines', :js do visit_project_pipelines end - it 'has artifats' do + it 'has artifacts' do expect(page).to have_selector('.build-artifacts') end diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index c2c304589c9..b23fd8ccdc6 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -170,8 +170,9 @@ describe PipelinesFinder do context 'when order_by and sort are specified' do context 'when order_by user_id' do - let(:params) { { order_by: 'user_id', sort: 'asc' } } - let!(:pipelines) { Array.new(2) { create(:ci_pipeline, project: project, user: create(:user)) } } + let(:params) { { order_by: 'user_id', sort: 'asc' } } + let(:users) { Array.new(2) { create(:user, developer_projects: [project]) } } + let!(:pipelines) { users.map { |user| create(:ci_pipeline, project: project, user: user) } } it 'sorts as user_id: :asc' do is_expected.to match_array(pipelines) diff --git a/spec/frontend/issue_show/components/pinned_links_spec.js b/spec/frontend/issue_show/components/pinned_links_spec.js new file mode 100644 index 00000000000..50041667a61 --- /dev/null +++ b/spec/frontend/issue_show/components/pinned_links_spec.js @@ -0,0 +1,91 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { GlLink } from '@gitlab/ui'; +import PinnedLinks from '~/issue_show/components/pinned_links.vue'; + +const localVue = createLocalVue(); + +const plainZoomUrl = 'https://zoom.us/j/123456789'; +const vanityZoomUrl = 'https://gitlab.zoom.us/j/123456789'; +const startZoomUrl = 'https://zoom.us/s/123456789'; +const personalZoomUrl = 'https://zoom.us/my/hunter-zoloman'; +const randomUrl = 'https://zoom.us.com'; + +describe('PinnedLinks', () => { + let wrapper; + + const link = { + get text() { + return wrapper.find(GlLink).text(); + }, + get href() { + return wrapper.find(GlLink).attributes('href'); + }, + }; + + const createComponent = props => { + wrapper = shallowMount(localVue.extend(PinnedLinks), { + localVue, + sync: false, + propsData: { + descriptionHtml: '', + ...props, + }, + }); + }; + + it('displays Zoom link', () => { + createComponent({ + descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`, + }); + + expect(link.text).toBe('Join Zoom meeting'); + }); + + it('detects plain Zoom link', () => { + createComponent({ + descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`, + }); + + expect(link.href).toBe(plainZoomUrl); + }); + + it('detects vanity Zoom link', () => { + createComponent({ + descriptionHtml: `<a href="${vanityZoomUrl}">Zoom</a>`, + }); + + expect(link.href).toBe(vanityZoomUrl); + }); + + it('detects Zoom start meeting link', () => { + createComponent({ + descriptionHtml: `<a href="${startZoomUrl}">Zoom</a>`, + }); + + expect(link.href).toBe(startZoomUrl); + }); + + it('detects personal Zoom room link', () => { + createComponent({ + descriptionHtml: `<a href="${personalZoomUrl}">Zoom</a>`, + }); + + expect(link.href).toBe(personalZoomUrl); + }); + + it('only renders final Zoom link in description', () => { + createComponent({ + descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a><a href="${vanityZoomUrl}">Zoom</a>`, + }); + + expect(link.href).toBe(vanityZoomUrl); + }); + + it('does not render for other links', () => { + createComponent({ + descriptionHtml: `<a href="${randomUrl}">Some other link</a>`, + }); + + expect(wrapper.find(GlLink).exists()).toBe(false); + }); +}); diff --git a/spec/frontend/vue_shared/components/pagination_links_spec.js b/spec/frontend/vue_shared/components/pagination_links_spec.js index d0cb3731050..efa5825d92f 100644 --- a/spec/frontend/vue_shared/components/pagination_links_spec.js +++ b/spec/frontend/vue_shared/components/pagination_links_spec.js @@ -1,59 +1,77 @@ -import Vue from 'vue'; +import { mount, createLocalVue } from '@vue/test-utils'; +import { GlPagination } from '@gitlab/ui'; import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; -import { s__ } from '~/locale'; -import mountComponent from '../../helpers/vue_mount_component_helper'; +import { + PREV, + NEXT, + LABEL_FIRST_PAGE, + LABEL_PREV_PAGE, + LABEL_NEXT_PAGE, + LABEL_LAST_PAGE, +} from '~/vue_shared/components/pagination/constants'; + +const localVue = createLocalVue(); describe('Pagination links component', () => { - const paginationLinksComponent = Vue.extend(PaginationLinks); - const change = page => page; const pageInfo = { page: 3, perPage: 5, total: 30, }; const translations = { - firstText: s__('Pagination|« First'), - prevText: s__('Pagination|Prev'), - nextText: s__('Pagination|Next'), - lastText: s__('Pagination|Last »'), + prevText: PREV, + nextText: NEXT, + labelFirstPage: LABEL_FIRST_PAGE, + labelPrevPage: LABEL_PREV_PAGE, + labelNextPage: LABEL_NEXT_PAGE, + labelLastPage: LABEL_LAST_PAGE, }; - let paginationLinks; + let wrapper; let glPagination; - let destinationComponent; + let changeMock; - beforeEach(() => { - paginationLinks = mountComponent(paginationLinksComponent, { - change, - pageInfo, + const createComponent = () => { + changeMock = jest.fn(); + wrapper = mount(PaginationLinks, { + propsData: { + change: changeMock, + pageInfo, + }, + localVue, + sync: false, }); - [glPagination] = paginationLinks.$children; - [destinationComponent] = glPagination.$children; + }; + + beforeEach(() => { + createComponent(); + glPagination = wrapper.find(GlPagination); }); afterEach(() => { - paginationLinks.$destroy(); + wrapper.destroy(); }); it('should provide translated text to GitLab UI pagination', () => { Object.entries(translations).forEach(entry => { - expect(destinationComponent[entry[0]]).toBe(entry[1]); + expect(glPagination.vm[entry[0]]).toBe(entry[1]); }); }); - it('should pass change to GitLab UI pagination', () => { - expect(Object.is(glPagination.change, change)).toBe(true); + it('should call change when page changes', () => { + wrapper.find('a').trigger('click'); + expect(changeMock).toHaveBeenCalled(); }); it('should pass page from pageInfo to GitLab UI pagination', () => { - expect(destinationComponent.value).toBe(pageInfo.page); + expect(glPagination.vm.value).toBe(pageInfo.page); }); it('should pass per page from pageInfo to GitLab UI pagination', () => { - expect(destinationComponent.perPage).toBe(pageInfo.perPage); + expect(glPagination.vm.perPage).toBe(pageInfo.perPage); }); it('should pass total items from pageInfo to GitLab UI pagination', () => { - expect(destinationComponent.totalRows).toBe(pageInfo.total); + expect(glPagination.vm.totalItems).toBe(pageInfo.total); }); }); diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb index bae560829cc..210932f8488 100644 --- a/spec/graphql/types/issue_type_spec.rb +++ b/spec/graphql/types/issue_type_spec.rb @@ -7,6 +7,8 @@ describe GitlabSchema.types['Issue'] do it { expect(described_class).to require_graphql_authorizations(:read_issue) } + it { expect(described_class.interfaces).to include(Types::Notes::NoteableType.to_graphql) } + it 'has specific fields' do %i[relative_position web_path web_url reference].each do |field_name| expect(described_class).to have_graphql_field(field_name) diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb index 89c12879074..fd1c782bcc5 100644 --- a/spec/graphql/types/merge_request_type_spec.rb +++ b/spec/graphql/types/merge_request_type_spec.rb @@ -5,6 +5,8 @@ describe GitlabSchema.types['MergeRequest'] do it { expect(described_class).to require_graphql_authorizations(:read_merge_request) } + it { expect(described_class.interfaces).to include(Types::Notes::NoteableType.to_graphql) } + describe 'nested head pipeline' do it { expect(described_class).to have_graphql_field(:head_pipeline) } end diff --git a/spec/graphql/types/notes/diff_position_type_spec.rb b/spec/graphql/types/notes/diff_position_type_spec.rb new file mode 100644 index 00000000000..2f8724d7f0d --- /dev/null +++ b/spec/graphql/types/notes/diff_position_type_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe GitlabSchema.types['DiffPosition'] do + it 'exposes the expected fields' do + expected_fields = [:head_sha, :base_sha, :start_sha, :file_path, :old_path, + :new_path, :position_type, :old_line, :new_line, :x, :y, + :width, :height] + + is_expected.to have_graphql_field(*expected_fields) + end +end diff --git a/spec/graphql/types/notes/discussion_type_spec.rb b/spec/graphql/types/notes/discussion_type_spec.rb new file mode 100644 index 00000000000..2a1eb0efd35 --- /dev/null +++ b/spec/graphql/types/notes/discussion_type_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe GitlabSchema.types['Discussion'] do + it { is_expected.to have_graphql_fields(:id, :created_at, :notes) } + + it { is_expected.to require_graphql_authorizations(:read_note) } +end diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb new file mode 100644 index 00000000000..8022b20f9dd --- /dev/null +++ b/spec/graphql/types/notes/note_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe GitlabSchema.types['Note'] do + it 'exposes the expected fields' do + expected_fields = [:id, :project, :author, :body, :created_at, + :updated_at, :discussion, :resolvable, :position, :user_permissions, + :resolved_by, :resolved_at, :system] + + is_expected.to have_graphql_fields(*expected_fields) + end + + it { is_expected.to expose_permissions_using(Types::PermissionTypes::Note) } + it { is_expected.to require_graphql_authorizations(:read_note) } +end diff --git a/spec/graphql/types/notes/noteable_type_spec.rb b/spec/graphql/types/notes/noteable_type_spec.rb new file mode 100644 index 00000000000..d10c79b5344 --- /dev/null +++ b/spec/graphql/types/notes/noteable_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Types::Notes::NoteableType do + it { is_expected.to have_graphql_fields(:notes, :discussions) } + + describe ".resolve_type" do + it 'knows the correct type for objects' do + expect(described_class.resolve_type(build(:issue), {})).to eq(Types::IssueType) + expect(described_class.resolve_type(build(:merge_request), {})).to eq(Types::MergeRequestType) + end + end +end diff --git a/spec/graphql/types/permission_types/note_spec.rb b/spec/graphql/types/permission_types/note_spec.rb new file mode 100644 index 00000000000..32d56eb1f7a --- /dev/null +++ b/spec/graphql/types/permission_types/note_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe GitlabSchema.types['NotePermissions'] do + it 'has the expected fields' do + expected_permissions = [ + :read_note, :create_note, :admin_note, :resolve_note, :award_emoji + ] + + is_expected.to have_graphql_fields(expected_permissions) + end +end diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 1aabf3c2132..fdf8bcee756 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -37,6 +37,8 @@ describe('diffs/components/app', () => { projectPath: 'namespace/project', currentUser: {}, changesEmptyStateIllustration: '', + dismissEndpoint: '', + showSuggestPopover: true, ...props, }, store, diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index f129fbb57a3..f973728cfe1 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -37,6 +37,7 @@ import actions, { toggleFullDiff, setFileCollapsed, setExpandedDiffLines, + setSuggestPopoverDismissed, } from '~/diffs/store/actions'; import eventHub from '~/notes/event_hub'; import * as types from '~/diffs/store/mutation_types'; @@ -68,12 +69,19 @@ describe('DiffsStoreActions', () => { it('should set given endpoint and project path', done => { const endpoint = '/diffs/set/endpoint'; const projectPath = '/root/project'; + const dismissEndpoint = '/-/user_callouts'; + const showSuggestPopover = false; testAction( setBaseConfig, - { endpoint, projectPath }, - { endpoint: '', projectPath: '' }, - [{ type: types.SET_BASE_CONFIG, payload: { endpoint, projectPath } }], + { endpoint, projectPath, dismissEndpoint, showSuggestPopover }, + { endpoint: '', projectPath: '', dismissEndpoint: '', showSuggestPopover: true }, + [ + { + type: types.SET_BASE_CONFIG, + payload: { endpoint, projectPath, dismissEndpoint, showSuggestPopover }, + }, + ], [], done, ); @@ -1080,4 +1088,30 @@ describe('DiffsStoreActions', () => { ); }); }); + + describe('setSuggestPopoverDismissed', () => { + it('commits SET_SHOW_SUGGEST_POPOVER', done => { + const state = { dismissEndpoint: `${gl.TEST_HOST}/-/user_callouts` }; + const mock = new MockAdapter(axios); + mock.onPost(state.dismissEndpoint).reply(200, {}); + + spyOn(axios, 'post').and.callThrough(); + + testAction( + setSuggestPopoverDismissed, + null, + state, + [{ type: types.SET_SHOW_SUGGEST_POPOVER }], + [], + () => { + expect(axios.post).toHaveBeenCalledWith(state.dismissEndpoint, { + feature_name: 'suggest_popover_dismissed', + }); + + mock.restore(); + done(); + }, + ); + }); + }); }); diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index fa193e1d3b9..9c13c7ceb7a 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -850,4 +850,14 @@ describe('DiffsStoreMutations', () => { expect(file.renderingLines).toBe(false); }); }); + + describe('SET_SHOW_SUGGEST_POPOVER', () => { + it('sets showSuggestPopover to false', () => { + const state = { showSuggestPopover: true }; + + mutations[types.SET_SHOW_SUGGEST_POPOVER](state); + + expect(state.showSuggestPopover).toBe(false); + }); + }); }); diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb index de6fcfe10f4..6b6b0eefab9 100644 --- a/spec/javascripts/fixtures/pipelines.rb +++ b/spec/javascripts/fixtures/pipelines.rb @@ -8,7 +8,7 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll let(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') } let(:commit) { create(:commit, project: project) } let(:commit_without_author) { RepoHelpers.another_sample_commit } - let!(:user) { create(:user, email: commit.author_email) } + let!(:user) { create(:user, developer_projects: [project], email: commit.author_email) } let!(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, user: user) } let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) } let!(:pipeline_without_commit) { create(:ci_pipeline, project: project, sha: '0000') } diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js index e98639bf21e..52bb5161123 100644 --- a/spec/javascripts/jobs/components/stages_dropdown_spec.js +++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js @@ -9,7 +9,6 @@ describe('Stages Dropdown', () => { const mockPipelineData = { id: 28029444, - iid: 123, details: { status: { details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', @@ -78,8 +77,8 @@ describe('Stages Dropdown', () => { expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy'); }); - it(`renders the pipeline info text like "Pipeline #123 (#12) for source_branch"`, () => { - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for ${pipeline.ref.name}`; + it(`renders the pipeline info text like "Pipeline #123 for source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for ${pipeline.ref.name}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); @@ -101,10 +100,10 @@ describe('Stages Dropdown', () => { }); }); - it(`renders the pipeline info text like "Pipeline #123 (#12) for !456 with source_branch into target_branch"`, () => { - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${ - pipeline.merge_request.iid - } with ${pipeline.merge_request.source_branch} into ${pipeline.merge_request.target_branch}`; + it(`renders the pipeline info text like "Pipeline #123 for !456 with source_branch into target_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + } into ${pipeline.merge_request.target_branch}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); @@ -144,10 +143,10 @@ describe('Stages Dropdown', () => { }); }); - it(`renders the pipeline info like "Pipeline #123 (#12) for !456 with source_branch"`, () => { - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${ - pipeline.merge_request.iid - } with ${pipeline.merge_request.source_branch}`; + it(`renders the pipeline info like "Pipeline #123 for !456 with source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + }`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 88b0bb206ee..3d40e94d219 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -960,7 +960,6 @@ export default { }, pipeline: { id: 140, - iid: 13, user: { name: 'Root', username: 'root', diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 1295d900de7..3a53ecacb88 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -46,15 +46,30 @@ describe('MergeRequestTabs', function() { describe('opensInNewTab', function() { var tabUrl; var windowTarget = '_blank'; + let clickTabParams; beforeEach(function() { loadFixtures('merge_requests/merge_request_with_task_list.html'); tabUrl = $('.commits-tab a').attr('href'); + + clickTabParams = { + metaKey: false, + ctrlKey: false, + which: 1, + stopImmediatePropagation: function() {}, + preventDefault: function() {}, + currentTarget: { + getAttribute: function(attr) { + return attr === 'href' ? tabUrl : null; + }, + }, + }; }); describe('meta click', () => { let metakeyEvent; + beforeEach(function() { metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true }); }); @@ -67,6 +82,8 @@ describe('MergeRequestTabs', function() { this.class.bindEvents(); $('.merge-request-tabs .commits-tab a').trigger(metakeyEvent); + + expect(window.open).toHaveBeenCalled(); }); it('opens page when commits badge is clicked', function() { @@ -77,6 +94,8 @@ describe('MergeRequestTabs', function() { this.class.bindEvents(); $('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent); + + expect(window.open).toHaveBeenCalled(); }); }); @@ -86,12 +105,9 @@ describe('MergeRequestTabs', function() { expect(name).toEqual(windowTarget); }); - this.class.clickTab({ - metaKey: false, - ctrlKey: true, - which: 1, - stopImmediatePropagation: function() {}, - }); + this.class.clickTab({ ...clickTabParams, metaKey: true }); + + expect(window.open).toHaveBeenCalled(); }); it('opens page tab in a new browser tab with Cmd+Click - Mac', function() { @@ -100,12 +116,9 @@ describe('MergeRequestTabs', function() { expect(name).toEqual(windowTarget); }); - this.class.clickTab({ - metaKey: true, - ctrlKey: false, - which: 1, - stopImmediatePropagation: function() {}, - }); + this.class.clickTab({ ...clickTabParams, ctrlKey: true }); + + expect(window.open).toHaveBeenCalled(); }); it('opens page tab in a new browser tab with Middle-click - Mac/PC', function() { @@ -114,12 +127,9 @@ describe('MergeRequestTabs', function() { expect(name).toEqual(windowTarget); }); - this.class.clickTab({ - metaKey: false, - ctrlKey: false, - which: 2, - stopImmediatePropagation: function() {}, - }); + this.class.clickTab({ ...clickTabParams, which: 2 }); + + expect(window.open).toHaveBeenCalled(); }); }); diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js index 8eef9166b8d..03ead6cd8ba 100644 --- a/spec/javascripts/pipelines/mock_data.js +++ b/spec/javascripts/pipelines/mock_data.js @@ -1,6 +1,5 @@ export const pipelineWithStages = { id: 20333396, - iid: 304399, user: { id: 128633, name: 'Rémy Coutable', diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js index 88c0137dc58..aa196af2f33 100644 --- a/spec/javascripts/pipelines/pipeline_url_spec.js +++ b/spec/javascripts/pipelines/pipeline_url_spec.js @@ -13,7 +13,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: {}, }, @@ -29,7 +28,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: {}, }, @@ -49,7 +47,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: { latest: true, @@ -81,7 +78,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: { latest: true, @@ -104,7 +100,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: { failure_reason: true, diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index a2308b0dfdb..75017d20473 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -103,7 +103,7 @@ describe('MRWidgetPipeline', () => { it('should render pipeline ID', () => { expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`, + `#${mockData.pipeline.id}`, ); }); @@ -150,7 +150,7 @@ describe('MRWidgetPipeline', () => { it('should render pipeline ID', () => { expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`, + `#${mockData.pipeline.id}`, ); }); @@ -222,9 +222,9 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ - pipeline.details.status.label - } for ${pipeline.commit.short_id} on ${mockCopy.source_branch_link}`; + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on ${mockCopy.source_branch_link}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); @@ -247,11 +247,11 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ - pipeline.details.status.label - } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${ - pipeline.merge_request.source_branch - } into ${pipeline.merge_request.target_branch}`; + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch} into ${ + pipeline.merge_request.target_branch + }`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); @@ -274,11 +274,9 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ - pipeline.details.status.label - } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${ - pipeline.merge_request.source_branch - }`; + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 3c9a5cece90..48f812f0db4 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -61,7 +61,6 @@ export default { "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22", pipeline: { id: 172, - iid: 32, user: { name: 'Administrator', username: 'root', @@ -243,8 +242,6 @@ export default { export const mockStore = { pipeline: { id: 0, - iid: 0, - path: '/root/acets-app/pipelines/0', details: { status: { details_path: '/root/review-app-tester/pipelines/66', @@ -262,8 +259,6 @@ export const mockStore = { }, mergePipeline: { id: 1, - iid: 1, - path: '/root/acets-app/pipelines/0', details: { status: { details_path: '/root/review-app-tester/pipelines/66', diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 08f7a17515e..ac2fb16bd10 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -544,7 +544,6 @@ describe('mrWidgetOptions', () => { ]; const deploymentMockData = { id: 15, - iid: 7, name: 'review/diplo', url: '/root/acets-review-apps/environments/15', stop_url: '/root/acets-review-apps/environments/15/stop', @@ -591,7 +590,6 @@ describe('mrWidgetOptions', () => { vm.mr.state = 'merged'; vm.mr.mergePipeline = { id: 127, - iid: 35, user: { id: 1, name: 'Administrator', diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index d4be2451f0b..af92e5f5ae2 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -22,13 +22,13 @@ describe('Markdown field header component', () => { 'Add bold text', 'Add italic text', 'Insert a quote', + 'Insert suggestion', 'Insert code', 'Add a link', 'Add a bullet list', 'Add a numbered list', 'Add a task list', 'Add a table', - 'Insert suggestion', 'Go full screen', ]; const elements = vm.$el.querySelectorAll('.toolbar-btn'); diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb index 3debd42ac65..50cb45c39d1 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Chain::Build do set(:project) { create(:project, :repository) } - set(:user) { create(:user) } + set(:user) { create(:user, developer_projects: [project]) } let(:pipeline) { Ci::Pipeline.new } let(:variables_attributes) do diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb index 97da66d2bcc..a6fdec832a3 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb @@ -50,21 +50,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:left_value) { 'my-string' } let(:right_value) { Gitlab::UntrustedRegexp.new('something') } - it { is_expected.to eq(nil) } + it { is_expected.to eq(false) } end context 'when left and right match' do let(:left_value) { 'my-awesome-string' } let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') } - it { is_expected.to eq(3) } + it { is_expected.to eq(true) } end context 'when left is nil' do let(:left_value) { nil } let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') } - it { is_expected.to eq(nil) } + it { is_expected.to eq(false) } end context 'when left is a multiline string and matches right' do @@ -78,7 +78,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') } - it { is_expected.to eq(24) } + it { is_expected.to eq(true) } end context 'when left is a multiline string and does not match right' do @@ -92,7 +92,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') } - it { is_expected.to eq(nil) } + it { is_expected.to eq(false) } end context 'when a matching pattern uses regex flags' do @@ -104,7 +104,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') } - it { is_expected.to eq(3) } + it { is_expected.to eq(true) } end context 'when a non-matching pattern uses regex flags' do @@ -116,7 +116,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') } - it { is_expected.to eq(nil) } + it { is_expected.to eq(false) } end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index 057e2f3fbe8..a2c2e3653d5 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -41,17 +41,17 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do 'null == $UNDEFINED_VARIABLE' | true '$PRESENT_VARIABLE' | 'my variable' '$UNDEFINED_VARIABLE' | nil - "$PRESENT_VARIABLE =~ /var.*e$/" | 3 - '$PRESENT_VARIABLE =~ /va\r.*e$/' | nil - '$PRESENT_VARIABLE =~ /va\/r.*e$/' | nil - "$PRESENT_VARIABLE =~ /var.*e$/" | 3 - "$PRESENT_VARIABLE =~ /^var.*/" | nil - "$EMPTY_VARIABLE =~ /var.*/" | nil - "$UNDEFINED_VARIABLE =~ /var.*/" | nil - "$PRESENT_VARIABLE =~ /VAR.*/i" | 3 - '$PATH_VARIABLE =~ /path\/variable/' | 2 - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | 0 - '$FULL_PATH_VARIABLE =~ /\\/path\\/variable\\/value$/' | 7 + "$PRESENT_VARIABLE =~ /var.*e$/" | true + '$PRESENT_VARIABLE =~ /va\r.*e$/' | false + '$PRESENT_VARIABLE =~ /va\/r.*e$/' | false + "$PRESENT_VARIABLE =~ /var.*e$/" | true + "$PRESENT_VARIABLE =~ /^var.*/" | false + "$EMPTY_VARIABLE =~ /var.*/" | false + "$UNDEFINED_VARIABLE =~ /var.*/" | false + "$PRESENT_VARIABLE =~ /VAR.*/i" | true + '$PATH_VARIABLE =~ /path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true + '$FULL_PATH_VARIABLE =~ /\\/path\\/variable\\/value$/' | true '$PRESENT_VARIABLE != "my variable"' | false '"my variable" != $PRESENT_VARIABLE' | false '$PRESENT_VARIABLE != null' | true @@ -82,7 +82,7 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do '"string" && "string"' | 'string' 'null && null' | nil - '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | nil + '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | false '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | 'my variable' '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | true '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | '' @@ -90,17 +90,17 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do '$UNDEFINED_VARIABLE && $EMPTY_VARIABLE' | nil '$UNDEFINED_VARIABLE && $PRESENT_VARIABLE' | nil - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | 2 - '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | nil - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil - '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | false + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | false + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | false - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 0 - '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 2 - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | 0 - '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | nil + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | false - '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | 0 + '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | true '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | true '$PRESENT_VARIABLE != "nope" || $EMPTY_VARIABLE == ""' | true @@ -117,21 +117,6 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do it "evaluates to `#{params[:value].inspect}`" do expect(subject.evaluate).to eq(value) end - - # This test is used to ensure that our parser - # returns exactly the same results as if we - # were evaluating using ruby's `eval` - context 'when using Ruby eval' do - let(:expression_ruby) do - expression - .gsub(/null/, 'nil') - .gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/) { "variables['#{Regexp.last_match(1)}']" } - end - - it 'behaves exactly the same' do - expect(instance_eval(expression_ruby)).to eq(subject.evaluate) - end - end end context 'with the ci_variables_complex_expressions feature flag disabled' do diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb index e8fc67acf05..c738cc49c1f 100644 --- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb @@ -4,5 +4,41 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::CodeStage do let(:stage_name) { :code } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) } + let!(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') } + let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') } + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago) + issue_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago) + issue_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago) + create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1) + create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2) + end + it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes merge requests that closes issues' do + result = stage.events + + expect(result.count).to eq(2) + expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title) + end + end end diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb index 397dd4e5d2c..f8b103c0fab 100644 --- a/spec/lib/gitlab/cycle_analytics/events_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb @@ -53,20 +53,28 @@ describe 'cycle analytics events' do describe '#plan_events' do let(:stage) { :plan } - it 'has a title' do - expect(events.first[:title]).not_to be_nil + before do + create_commit_referencing_issue(context) end - it 'has a sha short ID' do - expect(events.first[:short_sha]).not_to be_nil + it 'has the total time' do + expect(events.first[:total_time]).not_to be_empty + end + + it 'has a title' do + expect(events.first[:title]).to eq(context.title) end it 'has the URL' do - expect(events.first[:commit_url]).not_to be_nil + expect(events.first[:url]).not_to be_nil end - it 'has the total time' do - expect(events.first[:total_time]).not_to be_empty + it 'has an iid' do + expect(events.first[:iid]).to eq(context.iid.to_s) + end + + it 'has a created_at timestamp' do + expect(events.first[:created_at]).to end_with('ago') end it "has the author's URL" do @@ -78,12 +86,13 @@ describe 'cycle analytics events' do end it "has the author's name" do - expect(events.first[:author][:name]).not_to be_nil + expect(events.first[:author][:name]).to eq(context.author.name) end end describe '#code_events' do let(:stage) { :code } + let!(:merge_request) { MergeRequest.first } before do create_commit_referencing_issue(context) @@ -122,6 +131,7 @@ describe 'cycle analytics events' do let(:stage) { :test } let(:merge_request) { MergeRequest.first } + let!(:context) { create(:issue, project: project, created_at: 2.days.ago) } let!(:pipeline) do create(:ci_pipeline, @@ -137,6 +147,7 @@ describe 'cycle analytics events' do pipeline.run! pipeline.succeed! + merge_merge_requests_closing_issue(user, project, context) end it 'has the name' do @@ -180,6 +191,10 @@ describe 'cycle analytics events' do let(:stage) { :review } let!(:context) { create(:issue, project: project, created_at: 2.days.ago) } + before do + merge_merge_requests_closing_issue(user, project, context) + end + it 'has the total time' do expect(events.first[:total_time]).not_to be_empty end diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb index 3127f01989d..3b6af9cbaed 100644 --- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb @@ -3,6 +3,37 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::IssueStage do let(:stage_name) { :issue } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) } + let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) } + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago ) + issue_2.metrics.update!(first_added_to_board_at: 30.minutes.ago) + issue_3.metrics.update!(first_added_to_board_at: 15.minutes.ago) + end it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes issues with metrics' do + result = stage.events + + expect(result.count).to eq(3) + expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title, issue_3.title) + end + end end diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb index 4c715921ad6..506a8160412 100644 --- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb @@ -3,6 +3,37 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::PlanStage do let(:stage_name) { :plan } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) } + let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) } + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago) + issue_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago) + issue_3.metrics.update!(first_added_to_board_at: 15.minutes.ago) + end it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes issues with metrics' do + result = stage.events + + expect(result.count).to eq(2) + expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title) + end + end end diff --git a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb index 1412c8dfa08..f072a9644e8 100644 --- a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb @@ -3,6 +3,43 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::ReviewStage do let(:stage_name) { :review } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) } + let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') } + let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') } + let!(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') } + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + mr_1.metrics.update!(merged_at: 30.minutes.ago) + mr_2.metrics.update!(merged_at: 10.minutes.ago) + + create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1) + create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2) + create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3) + end it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes merge requests that close issues' do + result = stage.events + + expect(result.count).to eq(2) + expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title) + end + end end diff --git a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb index 08425acbfc8..1a4b572cc11 100644 --- a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' shared_examples 'base stage' do + ISSUES_MEDIAN = 30.minutes.to_i + let(:stage) { described_class.new(project: double, options: {}) } before do diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb index 8154b3ac701..17d5fbb9733 100644 --- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb @@ -4,5 +4,46 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::StagingStage do let(:stage_name) { :staging } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) } + let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') } + let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') } + let(:build_1) { create(:ci_build, project: project) } + let(:build_2) { create(:ci_build, project: project) } + + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id) + mr_2.metrics.update!(merged_at: 60.minutes.ago, first_deployed_to_production_at: 30.minutes.ago, pipeline_id: build_2.commit_id) + mr_3.metrics.update!(merged_at: 10.minutes.ago, first_deployed_to_production_at: 3.days.ago, pipeline_id: create(:ci_build, project: project).commit_id) + + create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1) + create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2) + create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3) + end + it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes builds connected to merge request' do + result = stage.events + + expect(result.count).to eq(2) + expect(result.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name) + end + end end diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index cc4faf6f10b..aea02d21048 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -46,6 +46,9 @@ describe Gitlab::Diff::Position do ) end + it { is_expected.to be_on_text } + it { is_expected.not_to be_on_image } + describe "#diff_file" do it "returns the correct diff file" do diff_file = subject.diff_file(project.repository) @@ -91,6 +94,9 @@ describe Gitlab::Diff::Position do ) end + it { is_expected.not_to be_on_text } + it { is_expected.to be_on_image } + it "returns the correct diff file" do diff_file = subject.diff_file(project.repository) diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb index 8229f0eb794..47e6f5d4220 100644 --- a/spec/lib/gitlab/gpg/commit_spec.rb +++ b/spec/lib/gitlab/gpg/commit_spec.rb @@ -109,6 +109,89 @@ describe Gitlab::Gpg::Commit do end end + context 'valid key signed using recent version of Gnupg' do + let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first } + + let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) } + + let!(:gpg_key) do + create :gpg_key, key: GpgHelpers::User1.public_key, user: user + end + + let!(:crypto) { instance_double(GPGME::Crypto) } + + before do + fake_signature = [ + GpgHelpers::User1.signed_commit_signature, + GpgHelpers::User1.signed_commit_base_data + ] + + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return(fake_signature) + end + + it 'returns a valid signature' do + verified_signature = double('verified-signature', fingerprint: GpgHelpers::User1.fingerprint, valid?: true) + allow(GPGME::Crypto).to receive(:new).and_return(crypto) + allow(crypto).to receive(:verify).and_return(verified_signature) + + signature = described_class.new(commit).signature + + expect(signature).to have_attributes( + commit_sha: commit_sha, + project: project, + gpg_key: gpg_key, + gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid, + gpg_key_user_name: GpgHelpers::User1.names.first, + gpg_key_user_email: GpgHelpers::User1.emails.first, + verification_status: 'verified' + ) + end + end + + context 'valid key signed using older version of Gnupg' do + let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first } + + let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) } + + let!(:gpg_key) do + create :gpg_key, key: GpgHelpers::User1.public_key, user: user + end + + let!(:crypto) { instance_double(GPGME::Crypto) } + + before do + fake_signature = [ + GpgHelpers::User1.signed_commit_signature, + GpgHelpers::User1.signed_commit_base_data + ] + + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return(fake_signature) + end + + it 'returns a valid signature' do + keyid = GpgHelpers::User1.fingerprint.last(16) + verified_signature = double('verified-signature', fingerprint: keyid, valid?: true) + allow(GPGME::Crypto).to receive(:new).and_return(crypto) + allow(crypto).to receive(:verify).and_return(verified_signature) + + signature = described_class.new(commit).signature + + expect(signature).to have_attributes( + commit_sha: commit_sha, + project: project, + gpg_key: gpg_key, + gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid, + gpg_key_user_name: GpgHelpers::User1.names.first, + gpg_key_user_email: GpgHelpers::User1.emails.first, + verification_status: 'verified' + ) + end + end + context 'commit signed with a subkey' do let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User3.emails.first } diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 2242543daad..002359e5cc0 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -175,6 +175,8 @@ deploy_keys: services: - project - service_hook +- jira_tracker_data +- issue_tracker_data hooks: - project - web_hook_logs diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb index bdcb5914575..29fe1ae8d9c 100644 --- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb @@ -49,7 +49,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi describe '.find_all_paths' do let(:all_dashboard_paths) { described_class.find_all_paths(project) } - let(:system_dashboard) { { path: system_dashboard_path, default: true } } + let(:system_dashboard) { { path: system_dashboard_path, display_name: 'Default', default: true } } it 'includes only the system dashboard by default' do expect(all_dashboard_paths).to eq([system_dashboard]) @@ -60,7 +60,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi let(:project) { project_with_dashboard(dashboard_path) } it 'includes system and project dashboards' do - project_dashboard = { path: dashboard_path, default: false } + project_dashboard = { path: dashboard_path, display_name: 'test.yml', default: false } expect(all_dashboard_paths).to contain_exactly(system_dashboard, project_dashboard) end diff --git a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb index 794ac5f109b..57d82421b5d 100644 --- a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb @@ -59,4 +59,29 @@ describe Gitlab::Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_m it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity end end + + describe '::all_dashboard_paths' do + let(:all_dashboards) { described_class.all_dashboard_paths(project) } + + context 'when there are no project dashboards' do + it 'returns an empty array' do + expect(all_dashboards).to be_empty + end + end + + context 'when there are project dashboards available' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:project) { project_with_dashboard(dashboard_path) } + + it 'returns the dashboard attributes' do + expect(all_dashboards).to eq( + [{ + path: dashboard_path, + display_name: 'test.yml', + default: false + }] + ) + end + end + end end diff --git a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb index 2648fe990de..2af745bd4d7 100644 --- a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb @@ -29,4 +29,18 @@ describe Gitlab::Metrics::Dashboard::SystemDashboardService, :use_clean_rails_me it_behaves_like 'valid dashboard service response' end end + + describe '::all_dashboard_paths' do + it 'returns the dashboard attributes' do + all_dashboards = described_class.all_dashboard_paths(project) + + expect(all_dashboards).to eq( + [{ + path: described_class::SYSTEM_DASHBOARD_PATH, + display_name: described_class::SYSTEM_DASHBOARD_NAME, + default: true + }] + ) + end + end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index a8701f0efa4..c4e54be673f 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2736,7 +2736,7 @@ describe Ci::Pipeline, :mailer do create(:ci_pipeline, project: project, sha: project.commit('master').sha, - user: create(:user)) + user: project.owner) end before do diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb index 0d02165787a..22d4dab0617 100644 --- a/spec/models/discussion_spec.rb +++ b/spec/models/discussion_spec.rb @@ -29,4 +29,12 @@ describe Discussion do ]) end end + + describe 'authorization' do + it 'delegates to the first note' do + policy = DeclarativePolicy.policy_for(instance_double(User, id: 1), subject) + + expect(policy).to be_a(NotePolicy) + end + end end diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb index 3d4d4b7d795..85bfc3f1387 100644 --- a/spec/models/lfs_object_spec.rb +++ b/spec/models/lfs_object_spec.rb @@ -3,6 +3,20 @@ require 'spec_helper' describe LfsObject do + it 'has a distinct has_many :projects relation through lfs_objects_projects' do + lfs_object = create(:lfs_object) + project = create(:project) + [:project, :design].each do |repository_type| + create(:lfs_objects_project, project: project, + lfs_object: lfs_object, + repository_type: repository_type) + end + + expect(lfs_object.lfs_objects_projects.size).to eq(2) + expect(lfs_object.projects.size).to eq(1) + expect(lfs_object.projects.to_a).to eql([project]) + end + describe '#local_store?' do it 'returns true when file_store is equal to LfsObjectUploader::Store::LOCAL' do subject.file_store = LfsObjectUploader::Store::LOCAL diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb index 3e86ee38566..e320f873989 100644 --- a/spec/models/lfs_objects_project_spec.rb +++ b/spec/models/lfs_objects_project_spec.rb @@ -20,8 +20,8 @@ describe LfsObjectsProject do it 'validates object id' do is_expected.to validate_uniqueness_of(:lfs_object_id) - .scoped_to(:project_id) - .with_message("already exists in project") + .scoped_to(:project_id, :repository_type) + .with_message("already exists in repository") end end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index a04b984c1f6..a1bd0855708 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -301,7 +301,7 @@ describe HipchatService do end context 'pipeline events' do - let(:pipeline) { create(:ci_empty_pipeline, user: create(:user)) } + let(:pipeline) { create(:ci_empty_pipeline, user: project.owner) } let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } context 'for failed' do diff --git a/spec/models/project_services/issue_tracker_data_spec.rb b/spec/models/project_services/issue_tracker_data_spec.rb new file mode 100644 index 00000000000..aaab654f874 --- /dev/null +++ b/spec/models/project_services/issue_tracker_data_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe IssueTrackerData do + let(:service) { create(:custom_issue_tracker_service, active: false, properties: {}) } + + describe 'Associations' do + it { is_expected.to belong_to :service } + end + + describe 'Validations' do + subject { described_class.new(service: service) } + + context 'url validations' do + context 'when service is inactive' do + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + end + + context 'when service is active' do + before do + service.update(active: true) + end + + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + end + end + end +end diff --git a/spec/models/project_services/jira_tracker_data_spec.rb b/spec/models/project_services/jira_tracker_data_spec.rb new file mode 100644 index 00000000000..1b6ece8531b --- /dev/null +++ b/spec/models/project_services/jira_tracker_data_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe JiraTrackerData do + let(:service) { create(:jira_service, active: false, properties: {}) } + + describe 'Associations' do + it { is_expected.to belong_to(:service) } + end + + describe 'Validations' do + subject { described_class.new(service: service) } + + context 'jira_issue_transition_id' do + it { is_expected.to allow_value(nil).for(:jira_issue_transition_id) } + it { is_expected.to allow_value('1,2,3').for(:jira_issue_transition_id) } + it { is_expected.to allow_value('1;2;3').for(:jira_issue_transition_id) } + it { is_expected.not_to allow_value('a,b,cd').for(:jira_issue_transition_id) } + end + + context 'url validations' do + context 'when service is inactive' do + it { is_expected.not_to validate_presence_of(:url) } + it { is_expected.not_to validate_presence_of(:username) } + it { is_expected.not_to validate_presence_of(:password) } + end + + context 'when service is active' do + before do + service.update(active: true) + end + + it_behaves_like 'issue tracker service URL attribute', :url + + it { is_expected.to validate_presence_of(:url) } + it { is_expected.to validate_presence_of(:username) } + it { is_expected.to validate_presence_of(:password) } + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index aad08b9d4aa..e6d5e8fc320 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -103,6 +103,20 @@ describe Project do expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project) end + it 'has a distinct has_many :lfs_objects relation through lfs_objects_projects' do + project = create(:project) + lfs_object = create(:lfs_object) + [:project, :design].each do |repository_type| + create(:lfs_objects_project, project: project, + lfs_object: lfs_object, + repository_type: repository_type) + end + + expect(project.lfs_objects_projects.size).to eq(2) + expect(project.lfs_objects.size).to eq(1) + expect(project.lfs_objects.to_a).to eql([lfs_object]) + end + context 'after initialized' do it "has a project_feature" do expect(described_class.new.project_feature).to be_present @@ -1479,11 +1493,28 @@ describe Project do end context 'when set to INTERNAL in application settings' do + using RSpec::Parameterized::TableSyntax + before do stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) end it { is_expected.to eq(Gitlab::VisibilityLevel::INTERNAL) } + + where(:attribute_name, :value) do + :visibility | 'public' + :visibility_level | Gitlab::VisibilityLevel::PUBLIC + 'visibility' | 'public' + 'visibility_level' | Gitlab::VisibilityLevel::PUBLIC + end + + with_them do + it 'sets the visibility level' do + proj = described_class.new(attribute_name => value, name: 'test', path: 'test') + + expect(proj.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 64db32781fe..c9439b0846d 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -6,6 +6,8 @@ describe Service do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } + it { is_expected.to have_one :jira_tracker_data } + it { is_expected.to have_one :issue_tracker_data } end describe 'Validations' do diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb index 4be7a0266d1..bcf021f1dfd 100644 --- a/spec/policies/note_policy_spec.rb +++ b/spec/policies/note_policy_spec.rb @@ -133,6 +133,25 @@ describe NotePolicy do end end end + + context 'for discussions' do + let(:policy) { described_class.new(user, note.discussion) } + + it 'allows the author to manage the discussion' do + expect(policy).to be_allowed(:admin_note) + expect(policy).to be_allowed(:resolve_note) + expect(policy).to be_allowed(:read_note) + expect(policy).to be_allowed(:award_emoji) + end + + context 'when the user does not have access to the noteable' do + before do + noteable.update_attribute(:confidential, true) + end + + it_behaves_like 'a discussion with a private noteable' + end + end end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index ed0e82ef179..4b723a52b51 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -39,7 +39,7 @@ describe ProjectPolicy do admin_milestone admin_merge_request update_merge_request create_commit_status update_commit_status create_build update_build create_pipeline update_pipeline create_merge_request_from create_wiki push_code - resolve_note create_container_image update_container_image + resolve_note create_container_image update_container_image destroy_container_image create_environment create_deployment create_release update_release ] end diff --git a/spec/policies/project_statistics_policy_spec.rb b/spec/policies/project_statistics_policy_spec.rb new file mode 100644 index 00000000000..50dfbf7291b --- /dev/null +++ b/spec/policies/project_statistics_policy_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ProjectStatisticsPolicy do + using RSpec::Parameterized::TableSyntax + + describe '#rules' do + let(:external) { create(:user, :external) } + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + let(:developer) { create(:user) } + let(:maintainer) { create(:user) } + + let(:users) do + { + unauthenticated: nil, + non_member: create(:user), + guest: guest, + reporter: reporter, + developer: developer, + maintainer: maintainer + } + end + + where(:project_type, :user_type, :outcome) do + [ + # Public projects + [:public, :unauthenticated, false], + [:public, :non_member, false], + [:public, :guest, false], + [:public, :reporter, true], + [:public, :developer, true], + [:public, :maintainer, true], + + # Private project + [:private, :unauthenticated, false], + [:private, :non_member, false], + [:private, :guest, false], + [:private, :reporter, true], + [:private, :developer, true], + [:private, :maintainer, true], + + # Internal projects + [:internal, :unauthenticated, false], + [:internal, :non_member, false], + [:internal, :guest, false], + [:internal, :reporter, true], + [:internal, :developer, true], + [:internal, :maintainer, true] + ] + end + + with_them do + let(:user) { users[user_type] } + let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel.level_value(project_type.to_s)) } + let(:project_statistics) { create(:project_statistics, project: project) } + + subject { Ability.allowed?(user, :read_statistics, project_statistics) } + + before do + project.add_guest(guest) + project.add_reporter(reporter) + project.add_developer(developer) + project.add_maintainer(maintainer) + end + + it { is_expected.to eq(outcome) } + + context 'when the user is external' do + let(:user) { external } + + before do + unless [:unauthenticated, :non_member].include?(user_type) + project.add_user(external, user_type) + end + end + + it { is_expected.to eq(outcome) } + end + end + end +end diff --git a/spec/requests/api/container_registry_spec.rb b/spec/requests/api/container_registry_spec.rb index ea035a8be4a..4ad15ed6bea 100644 --- a/spec/requests/api/container_registry_spec.rb +++ b/spec/requests/api/container_registry_spec.rb @@ -201,10 +201,10 @@ describe API::ContainerRegistry do describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) } - it_behaves_like 'being disallowed', :developer + it_behaves_like 'being disallowed', :reporter - context 'for maintainer' do - let(:api_user) { maintainer } + context 'for developer' do + let(:api_user) { developer } before do stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true) diff --git a/spec/requests/api/graphql/project/issue/notes_spec.rb b/spec/requests/api/graphql/project/issue/notes_spec.rb new file mode 100644 index 00000000000..bfc89434370 --- /dev/null +++ b/spec/requests/api/graphql/project/issue/notes_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'getting notes for an issue' do + include GraphqlHelpers + + let(:noteable) { create(:issue) } + let(:noteable_data) { graphql_data['project']['issue'] } + + def noteable_query(noteable_fields) + <<~QRY + { + project(fullPath: "#{noteable.project.full_path}") { + issue(iid: "#{noteable.iid}") { + #{noteable_fields} + } + } + } + QRY + end + + it_behaves_like 'exposing regular notes on a noteable in GraphQL' +end diff --git a/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb new file mode 100644 index 00000000000..e260e4463f4 --- /dev/null +++ b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'getting notes for a merge request' do + include GraphqlHelpers + + let(:noteable) { create(:merge_request) } + + def noteable_query(noteable_fields) + <<~QRY + { + project(fullPath: "#{noteable.project.full_path}") { + id + mergeRequest(iid: "#{noteable.iid}") { + #{noteable_fields} + } + } + } + QRY + end + let(:noteable_data) { graphql_data['project']['mergeRequest'] } + + it_behaves_like "exposing regular notes on a noteable in GraphQL" + + context 'diff notes on a merge request' do + let(:project) { noteable.project } + let!(:note) { create(:diff_note_on_merge_request, noteable: noteable, project: project) } + let(:user) { note.author } + + let(:query) do + noteable_query( + <<~NOTES + notes { + edges { + node { + #{all_graphql_fields_for('Note')} + } + } + } + NOTES + ) + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: user) + end + end + + it 'includes the note' do + post_graphql(query, current_user: user) + + expect(graphql_data['project']['mergeRequest']['notes']['edges'].last['node']['body']) + .to eq(note.note) + end + + context 'the position of the diffnote' do + it 'includes a correct position' do + post_graphql(query, current_user: user) + + note_data = noteable_data['notes']['edges'].last['node'] + + expect(note_data['position']['positionType']).to eq('text') + expect(note_data['position']['newLine']).to be_present + expect(note_data['position']['x']).not_to be_present + expect(note_data['position']['y']).not_to be_present + expect(note_data['position']['width']).not_to be_present + expect(note_data['position']['height']).not_to be_present + end + + context 'with a note on an image' do + let(:note) { create(:image_diff_note_on_merge_request, noteable: noteable, project: project) } + + it 'includes a correct position' do + post_graphql(query, current_user: user) + + note_data = noteable_data['notes']['edges'].last['node'] + + expect(note_data['position']['positionType']).to eq('image') + expect(note_data['position']['x']).to be_present + expect(note_data['position']['y']).to be_present + expect(note_data['position']['width']).to be_present + expect(note_data['position']['height']).to be_present + expect(note_data['position']['newLine']).not_to be_present + end + end + end + end +end diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb index 8683fa1f390..14a3f37b779 100644 --- a/spec/requests/api/graphql/project/project_statistics_spec.rb +++ b/spec/requests/api/graphql/project/project_statistics_spec.rb @@ -34,10 +34,10 @@ describe 'rendering namespace statistics' do context 'when the project is public' do let(:project) { create(:project, :public) } - it 'includes the statistics regardless of the user' do + it 'hides statistics for unauthenticated requests' do post_graphql(query, current_user: nil) - expect(graphql_data['project']['statistics']).to be_present + expect(graphql_data['project']['statistics']).to be_blank end end end diff --git a/spec/requests/api/graphql/tasks/task_completion_status_spec.rb b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb new file mode 100644 index 00000000000..c457a6d7c25 --- /dev/null +++ b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'getting task completion status information' do + include GraphqlHelpers + + DESCRIPTION_0_DONE = '- [ ] task 1\n- [ ] task 2' + DESCRIPTION_1_DONE = '- [x] task 1\n- [ ] task 2' + DESCRIPTION_2_DONE = '- [x] task 1\n- [x] task 2' + + set(:user1) { create(:user) } + set(:project) { create(:project, :repository, :public) } + + let(:fields) do + <<~QUERY + taskCompletionStatus { + count, + completedCount + } + QUERY + end + + def create_task_completion_status_query_for(type, iid) + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field(type, { iid: iid }, fields) + ) + end + + shared_examples_for 'graphql task completion status provider' do |type| + it 'returns the expected task completion status' do + post_graphql(create_task_completion_status_query_for(type, item.iid), current_user: user1) + + expect(response).to have_gitlab_http_status(200) + + task_completion_status = graphql_data.dig('project', type, 'taskCompletionStatus') + expect(task_completion_status).not_to be_nil + expect(task_completion_status['count']).to eq(item.task_completion_status[:count]) + expect(task_completion_status['completedCount']).to eq(item.task_completion_status[:completed_count]) + end + end + + [DESCRIPTION_0_DONE, DESCRIPTION_1_DONE, DESCRIPTION_2_DONE].each do |desc| + context "with description #{desc}" do + context 'when type is issue' do + it_behaves_like 'graphql task completion status provider', 'issue' do + let(:item) { create(:issue, project: project, description: desc) } + end + end + + context 'when type is merge request' do + it_behaves_like 'graphql task completion status provider', 'mergeRequest' do + let(:item) { create(:merge_request, author: user1, source_project: project, description: desc) } + end + end + end + end +end diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 49412b628b3..25390f8a23e 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -32,10 +32,10 @@ describe 'cycle analytics events' do it 'lists the plan events' do get project_cycle_analytics_plan_path(project, format: :json) - first_mr_short_sha = project.merge_requests.sort_by_attribute(:created_asc).first.commits.first.short_id + first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s expect(json_response['events']).not_to be_empty - expect(json_response['events'].first['short_sha']).to eq(first_mr_short_sha) + expect(json_response['events'].first['iid']).to eq(first_issue_iid) end it 'lists the code events' do diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 867692d4d64..d9b61dfe503 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -1132,5 +1132,17 @@ describe Ci::CreatePipelineService do .with_message('Insufficient permissions to create a new pipeline') end end + + context 'when a user with permissions has been blocked' do + before do + user.block! + end + + it 'raises an error' do + expect { subject } + .to raise_error(described_class::CreateError) + .with_message('Insufficient permissions to create a new pipeline') + end + end end end diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb index 634f865e2d8..1e68b7956ea 100644 --- a/spec/services/ci/play_build_service_spec.rb +++ b/spec/services/ci/play_build_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ci::PlayBuildService, '#execute' do - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, :manual, pipeline: pipeline) } @@ -16,8 +16,6 @@ describe Ci::PlayBuildService, '#execute' do let(:project) { create(:project) } it 'allows user to play build if protected branch rules are met' do - project.add_developer(user) - create(:protected_branch, :developers_can_merge, name: build.ref, project: project) @@ -27,8 +25,6 @@ describe Ci::PlayBuildService, '#execute' do end it 'does not allow user with developer role to play build' do - project.add_developer(user) - expect { service.execute(build) } .to raise_error Gitlab::Access::AccessDeniedError end @@ -38,23 +34,21 @@ describe Ci::PlayBuildService, '#execute' do let(:project) { create(:project, :repository) } it 'allows user with developer role to play a build' do - project.add_developer(user) - service.execute(build) expect(build.reload).to be_pending end + + it 'prevents a blocked developer from playing a build' do + user.block! + + expect { service.execute(build) }.to raise_error(Gitlab::Access::AccessDeniedError) + end end context 'when build is a playable manual action' do let(:build) { create(:ci_build, :manual, pipeline: pipeline) } - - before do - project.add_developer(user) - - create(:protected_branch, :developers_can_merge, - name: build.ref, project: project) - end + let!(:branch) { create(:protected_branch, :developers_can_merge, name: build.ref, project: project) } it 'enqueues the build' do expect(service.execute(build)).to eq build @@ -70,13 +64,7 @@ describe Ci::PlayBuildService, '#execute' do context 'when build is not a playable manual action' do let(:build) { create(:ci_build, when: :manual, pipeline: pipeline) } - - before do - project.add_developer(user) - - create(:protected_branch, :developers_can_merge, - name: build.ref, project: project) - end + let!(:branch) { create(:protected_branch, :developers_can_merge, name: build.ref, project: project) } it 'duplicates the build' do duplicate = service.execute(build) @@ -94,6 +82,7 @@ describe Ci::PlayBuildService, '#execute' do end context 'when build is not action' do + let(:user) { create(:user) } let(:build) { create(:ci_build, :success, pipeline: pipeline) } it 'raises an error' do @@ -103,10 +92,8 @@ describe Ci::PlayBuildService, '#execute' do end context 'when user does not have ability to trigger action' do - before do - create(:protected_branch, :no_one_can_push, - name: build.ref, project: project) - end + let(:user) { create(:user) } + let!(:branch) { create(:protected_branch, :developers_can_merge, name: build.ref, project: project) } it 'raises an error' do expect { service.execute(build) } diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb index 888eea6e91e..9973d64930b 100644 --- a/spec/services/lfs/file_transformer_spec.rb +++ b/spec/services/lfs/file_transformer_spec.rb @@ -3,13 +3,13 @@ require "spec_helper" describe Lfs::FileTransformer do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository, :wiki_repo) } let(:repository) { project.repository } let(:file_content) { 'Test file content' } let(:branch_name) { 'lfs' } let(:file_path) { 'test_file.lfs' } - subject { described_class.new(project, branch_name) } + subject { described_class.new(project, repository, branch_name) } describe '#new_file' do context 'with lfs disabled' do @@ -100,6 +100,12 @@ describe Lfs::FileTransformer do end.to change { project.lfs_objects.count }.by(1) end + it 'saves the repository_type to LfsObjectsProject' do + subject.new_file(file_path, file_content) + + expect(project.lfs_objects_projects.first.repository_type).to eq('project') + end + context 'when LfsObject already exists' do let(:lfs_pointer) { Gitlab::Git::LfsPointerFile.new(file_content) } @@ -113,6 +119,56 @@ describe Lfs::FileTransformer do end.to change { project.lfs_objects.count }.by(1) end end + + context 'when the LfsObject is already linked to project' do + before do + subject.new_file(file_path, file_content) + end + + shared_examples 'a new LfsObject is not created' do + it do + expect do + second_service.new_file(file_path, file_content) + end.not_to change { project.lfs_objects.count } + end + end + + context 'and the service is called again with the same repository type' do + let(:second_service) { described_class.new(project, repository, branch_name) } + + include_examples 'a new LfsObject is not created' + + it 'does not create a new LfsObjectsProject record' do + expect do + second_service.new_file(file_path, file_content) + end.not_to change { project.lfs_objects_projects.count } + end + end + + context 'and the service is called again with a different repository type' do + let(:second_service) { described_class.new(project, project.wiki.repository, branch_name) } + + before do + expect(second_service).to receive(:lfs_file?).and_return(true) + end + + include_examples 'a new LfsObject is not created' + + it 'creates a new LfsObjectsProject record' do + expect do + second_service.new_file(file_path, file_content) + end.to change { project.lfs_objects_projects.count }.by(1) + end + + it 'sets the correct repository_type on the new LfsObjectsProject record' do + second_service.new_file(file_path, file_content) + + repository_types = project.lfs_objects_projects.order(:id).pluck(:repository_type) + + expect(repository_types).to eq(%w(project wiki)) + end + end + end end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 4b40c86574f..f25e2fe5e2b 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2217,10 +2217,12 @@ describe NotificationService, :mailer do let(:pipeline) { create(:ci_pipeline, :failed, project: project, user: pipeline_user) } it 'emails project owner and user that triggered the pipeline' do + project.add_developer(pipeline_user) + notification.autodevops_disabled(pipeline, [owner.email, pipeline_user.email]) - should_email(owner) - should_email(pipeline_user) + should_email(owner, times: 1) # Once for the disable pipeline. + should_email(pipeline_user, times: 2) # Once for the new permission, once for the disable. end end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index f54f9200661..a4c48991807 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -152,6 +152,33 @@ describe Projects::CreateService, '#execute' do end end + context 'default visibility level' do + let(:group) { create(:group, :private) } + + before do + stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) + group.add_developer(user) + + opts.merge!( + visibility: 'private', + name: 'test', + namespace: group, + path: 'foo' + ) + end + + it 'creates a private project' do + project = create_project(user, opts) + + expect(project).to respond_to(:errors) + + expect(project.errors.any?).to be(false) + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + expect(project.saved?).to be(true) + expect(project.valid?).to be(true) + end + end + context 'restricted visibility level' do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index e95c7f2a6d6..bcf6669f37d 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -157,7 +157,7 @@ module GraphqlHelpers when Array # multiplexed queries json_response.map { |response| response['errors'] } else - raise "Unkown GraphQL response type #{json_response.class}" + raise "Unknown GraphQL response type #{json_response.class}" end end diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb index c4ae62b25e4..d45377267f3 100644 --- a/spec/support/helpers/query_recorder.rb +++ b/spec/support/helpers/query_recorder.rb @@ -11,7 +11,7 @@ module ActiveRecord def show_backtrace(values) Rails.logger.debug("QueryRecorder SQL: #{values[:sql]}") - caller.each { |line| Rails.logger.debug(" --> #{line}") } + Gitlab::Profiler.clean_backtrace(caller).each { |line| Rails.logger.debug(" --> #{line}") } end def callback(name, start, finish, message_id, values) diff --git a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb new file mode 100644 index 00000000000..323d1c51ffd --- /dev/null +++ b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true +require 'spec_helper' + +shared_context 'exposing regular notes on a noteable in GraphQL' do + include GraphqlHelpers + + let(:note) do + create(:note, + noteable: noteable, + project: (noteable.project if noteable.respond_to?(:project))) + end + let(:user) { note.author } + + context 'for regular notes' do + let(:query) do + note_fields = <<~NOTES + notes { + edges { + node { + #{all_graphql_fields_for('Note')} + } + } + } + NOTES + + noteable_query(note_fields) + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: user) + end + end + + it 'includes the note' do + post_graphql(query, current_user: user) + + expect(noteable_data['notes']['edges'].first['node']['body']) + .to eq(note.note) + end + end + + context "for discussions" do + let(:query) do + discussion_fields = <<~DISCUSSIONS + discussions { + edges { + node { + #{all_graphql_fields_for('Discussion')} + } + } + } + DISCUSSIONS + + noteable_query(discussion_fields) + end + + let!(:reply) { create(:note, noteable: noteable, in_reply_to: note, discussion_id: note.discussion_id) } + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: user) + end + end + + it 'includes all discussion notes' do + post_graphql(query, current_user: user) + + discussion = noteable_data['discussions']['edges'].first['node'] + ids = discussion['notes']['edges'].map { |note_edge| note_edge['node']['id'] } + + expect(ids).to eq([note.to_global_id.to_s, reply.to_global_id.to_s]) + end + end +end diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb index d04affc7df1..623237b111a 100644 --- a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb +++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'notify/pipeline_failed_email.html.haml' do include Devise::Test::ControllerHelpers - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, :simple, source_project: project) } diff --git a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb index 079fb865d7b..81245239eba 100644 --- a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb +++ b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe 'notify/pipeline_failed_email.text.erb' do include Devise::Test::ControllerHelpers - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, :simple, source_project: project) } diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb index 8ee7f954d70..a876bf13e11 100644 --- a/spec/views/notify/pipeline_success_email.html.haml_spec.rb +++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'notify/pipeline_success_email.html.haml' do include Devise::Test::ControllerHelpers - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, :simple, source_project: project) } diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb index 457dd2e940f..1086546c10d 100644 --- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb +++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb @@ -27,7 +27,7 @@ describe 'projects/commit/_commit_box.html.haml' do render - expect(rendered).to have_text("Pipeline ##{third_pipeline.id} (##{third_pipeline.iid}) failed") + expect(rendered).to have_text("Pipeline ##{third_pipeline.id} failed") end end @@ -40,7 +40,7 @@ describe 'projects/commit/_commit_box.html.haml' do it 'shows correct pipeline description' do render - expect(rendered).to have_text "Pipeline ##{pipeline.id} (##{pipeline.iid}) " \ + expect(rendered).to have_text "Pipeline ##{pipeline.id} " \ 'waiting for manual action' end end diff --git a/spec/views/projects/jobs/_build.html.haml_spec.rb b/spec/views/projects/jobs/_build.html.haml_spec.rb index 97b25a6976f..1d58891036e 100644 --- a/spec/views/projects/jobs/_build.html.haml_spec.rb +++ b/spec/views/projects/jobs/_build.html.haml_spec.rb @@ -4,7 +4,7 @@ describe 'projects/ci/jobs/_build' do include Devise::Test::ControllerHelpers let(:project) { create(:project, :repository) } - let(:pipeline) { create(:ci_empty_pipeline, id: 1337, iid: 57, project: project, sha: project.commit.id) } + let(:pipeline) { create(:ci_empty_pipeline, id: 1337, project: project, sha: project.commit.id) } let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', stage_idx: 1, name: 'rspec 0:2', status: :pending) } before do @@ -15,14 +15,14 @@ describe 'projects/ci/jobs/_build' do it 'won\'t include a column with a link to its pipeline by default' do render partial: 'projects/ci/builds/build', locals: { build: build } - expect(rendered).not_to have_link('#1337 (#57)') - expect(rendered).not_to have_text('#1337 (#57) by API') + expect(rendered).not_to have_link('#1337') + expect(rendered).not_to have_text('#1337 by API') end it 'can include a column with a link to its pipeline' do render partial: 'projects/ci/builds/build', locals: { build: build, pipeline_link: true } - expect(rendered).to have_link('#1337 (#57)') - expect(rendered).to have_text('#1337 (#57) by API') + expect(rendered).to have_link('#1337') + expect(rendered).to have_text('#1337 by API') end end diff --git a/spec/workers/auto_devops/disable_worker_spec.rb b/spec/workers/auto_devops/disable_worker_spec.rb index 800a07e41a5..53113f286ba 100644 --- a/spec/workers/auto_devops/disable_worker_spec.rb +++ b/spec/workers/auto_devops/disable_worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe AutoDevops::DisableWorker, '#perform' do - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project, :repository, :auto_devops) } let(:auto_devops) { project.auto_devops } let(:pipeline) { create(:ci_pipeline, :failed, :auto_devops_source, project: project, user: user) } @@ -10,6 +10,7 @@ describe AutoDevops::DisableWorker, '#perform' do subject { described_class.new } before do + project.add_developer(user) stub_application_setting(auto_devops_enabled: true) auto_devops.update_attribute(:enabled, nil) end |