diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/frontend/environments/environment_item_spec.js | 4 | ||||
-rw-r--r-- | spec/frontend/lib/utils/datetime_utility_spec.js | 14 | ||||
-rw-r--r-- | spec/frontend/releases/components/app_index_spec.js | 109 | ||||
-rw-r--r-- | spec/frontend/snippets/components/snippet_header_spec.js | 3 | ||||
-rw-r--r-- | spec/lib/gitlab/http_spec.rb | 11 | ||||
-rw-r--r-- | spec/lib/gitlab/usage_data_spec.rb | 18 | ||||
-rw-r--r-- | spec/migrations/20200811130433_create_missing_vulnerabilities_issue_links_spec.rb | 160 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb | 26 |
8 files changed, 263 insertions, 82 deletions
diff --git a/spec/frontend/environments/environment_item_spec.js b/spec/frontend/environments/environment_item_spec.js index 5d374a162ab..1b429783821 100644 --- a/spec/frontend/environments/environment_item_spec.js +++ b/spec/frontend/environments/environment_item_spec.js @@ -3,7 +3,7 @@ import { format } from 'timeago.js'; import EnvironmentItem from '~/environments/components/environment_item.vue'; import PinComponent from '~/environments/components/environment_pin.vue'; import DeleteComponent from '~/environments/components/environment_delete.vue'; - +import { differenceInMilliseconds } from '~/lib/utils/datetime_utility'; import { environment, folder, tableData } from './mock_data'; describe('Environment item', () => { @@ -135,7 +135,7 @@ describe('Environment item', () => { }); describe('in the past', () => { - const pastDate = new Date(Date.now() - 100000); + const pastDate = new Date(differenceInMilliseconds(100000)); beforeEach(() => { factory({ propsData: { diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js index 9eb5587e83c..5b1fdea058b 100644 --- a/spec/frontend/lib/utils/datetime_utility_spec.js +++ b/spec/frontend/lib/utils/datetime_utility_spec.js @@ -653,3 +653,17 @@ describe('differenceInSeconds', () => { expect(datetimeUtility.differenceInSeconds(startDate, endDate)).toBe(expected); }); }); + +describe('differenceInMilliseconds', () => { + const startDateTime = new Date('2019-07-17T00:00:00.000Z'); + + it.each` + startDate | endDate | expected + ${startDateTime.getTime()} | ${new Date('2019-07-17T00:00:00.000Z')} | ${0} + ${startDateTime} | ${new Date('2019-07-17T12:00:00.000Z').getTime()} | ${43200000} + ${startDateTime} | ${new Date('2019-07-18T00:00:00.000Z').getTime()} | ${86400000} + ${new Date('2019-07-18T00:00:00.000Z')} | ${startDateTime.getTime()} | ${-86400000} + `('returns $expected for $endDate - $startDate', ({ startDate, endDate, expected }) => { + expect(datetimeUtility.differenceInMilliseconds(startDate, endDate)).toBe(expected); + }); +}); diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js index 8eafe07cb2f..0ffece36d90 100644 --- a/spec/frontend/releases/components/app_index_spec.js +++ b/spec/frontend/releases/components/app_index_spec.js @@ -1,12 +1,11 @@ import { range as rge } from 'lodash'; -import Vue from 'vue'; -import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import waitForPromises from 'helpers/wait_for_promises'; -import app from '~/releases/components/app_index.vue'; +import ReleasesApp from '~/releases/components/app_index.vue'; import createStore from '~/releases/stores'; import listModule from '~/releases/stores/modules/list'; import api from '~/api'; -import { resetStore } from '../stores/modules/list/helpers'; import { pageInfoHeadersWithoutPagination, pageInfoHeadersWithPagination, @@ -14,30 +13,37 @@ import { releases, } from '../mock_data'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); describe('Releases App ', () => { - const Component = Vue.extend(app); - let store; - let vm; - let releasesPagination; + let wrapper; + + const releasesPagination = rge(21).map(index => ({ + ...convertObjectPropsToCamelCase(release, { deep: true }), + tagName: `${index}.00`, + })); - const props = { + const defaultProps = { projectId: 'gitlab-ce', documentationPath: 'help/releases', illustrationPath: 'illustration/path', }; - beforeEach(() => { - store = createStore({ modules: { list: listModule } }); - releasesPagination = rge(21).map(index => ({ - ...convertObjectPropsToCamelCase(release, { deep: true }), - tagName: `${index}.00`, - })); - }); + const createComponent = (propsData = defaultProps) => { + const store = createStore({ modules: { list: listModule } }); + + wrapper = shallowMount(ReleasesApp, { + store, + localVue, + propsData, + }); + }; afterEach(() => { - resetStore(store); - vm.$destroy(); + wrapper.destroy(); }); describe('while loading', () => { @@ -47,16 +53,15 @@ describe('Releases App ', () => { // Need to defer the return value here to the next stack, // otherwise the loading state disappears before our test even starts. .mockImplementation(() => waitForPromises().then(() => ({ data: [], headers: {} }))); - vm = mountComponentWithStore(Component, { props, store }); + + createComponent(); }); it('renders loading icon', () => { - expect(vm.$el.querySelector('.js-loading')).not.toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); - - return waitForPromises(); + expect(wrapper.contains('.js-loading')).toBe(true); + expect(wrapper.contains('.js-empty-state')).toBe(false); + expect(wrapper.contains('.js-success-state')).toBe(false); + expect(wrapper.contains(TablePagination)).toBe(false); }); }); @@ -65,14 +70,15 @@ describe('Releases App ', () => { jest .spyOn(api, 'releases') .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); - vm = mountComponentWithStore(Component, { props, store }); + + createComponent(); }); it('renders success state', () => { - expect(vm.$el.querySelector('.js-loading')).toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).not.toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); + expect(wrapper.contains('.js-loading')).toBe(false); + expect(wrapper.contains('.js-empty-state')).toBe(false); + expect(wrapper.contains('.js-success-state')).toBe(true); + expect(wrapper.contains(TablePagination)).toBe(true); }); }); @@ -81,69 +87,60 @@ describe('Releases App ', () => { jest .spyOn(api, 'releases') .mockResolvedValue({ data: releasesPagination, headers: pageInfoHeadersWithPagination }); - vm = mountComponentWithStore(Component, { props, store }); + + createComponent(); }); it('renders success state', () => { - expect(vm.$el.querySelector('.js-loading')).toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).not.toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).not.toBeNull(); + expect(wrapper.contains('.js-loading')).toBe(false); + expect(wrapper.contains('.js-empty-state')).toBe(false); + expect(wrapper.contains('.js-success-state')).toBe(true); + expect(wrapper.contains(TablePagination)).toBe(true); }); }); describe('with empty request', () => { beforeEach(() => { jest.spyOn(api, 'releases').mockResolvedValue({ data: [], headers: {} }); - vm = mountComponentWithStore(Component, { props, store }); + + createComponent(); }); it('renders empty state', () => { - expect(vm.$el.querySelector('.js-loading')).toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).not.toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); + expect(wrapper.contains('.js-loading')).toBe(false); + expect(wrapper.contains('.js-empty-state')).toBe(true); + expect(wrapper.contains('.js-success-state')).toBe(false); }); }); describe('"New release" button', () => { - const findNewReleaseButton = () => vm.$el.querySelector('.js-new-release-btn'); + const findNewReleaseButton = () => wrapper.find('.js-new-release-btn'); beforeEach(() => { jest.spyOn(api, 'releases').mockResolvedValue({ data: [], headers: {} }); }); - const factory = additionalProps => { - vm = mountComponentWithStore(Component, { - props: { - ...props, - ...additionalProps, - }, - store, - }); - }; - describe('when the user is allowed to create a new Release', () => { const newReleasePath = 'path/to/new/release'; beforeEach(() => { - factory({ newReleasePath }); + createComponent({ ...defaultProps, newReleasePath }); }); it('renders the "New release" button', () => { - expect(findNewReleaseButton()).not.toBeNull(); + expect(findNewReleaseButton().exists()).toBe(true); }); it('renders the "New release" button with the correct href', () => { - expect(findNewReleaseButton().getAttribute('href')).toBe(newReleasePath); + expect(findNewReleaseButton().attributes('href')).toBe(newReleasePath); }); }); describe('when the user is not allowed to create a new Release', () => { - beforeEach(() => factory()); + beforeEach(() => createComponent()); it('does not render the "New release" button', () => { - expect(findNewReleaseButton()).toBeNull(); + expect(findNewReleaseButton().exists()).toBe(false); }); }); }); diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js index a997b337047..5836de1fdbe 100644 --- a/spec/frontend/snippets/components/snippet_header_spec.js +++ b/spec/frontend/snippets/components/snippet_header_spec.js @@ -5,6 +5,7 @@ import { Blob, BinaryBlob } from 'jest/blob/components/mock_data'; import waitForPromises from 'helpers/wait_for_promises'; import DeleteSnippetMutation from '~/snippets/mutations/deleteSnippet.mutation.graphql'; import SnippetHeader from '~/snippets/components/snippet_header.vue'; +import { differenceInMilliseconds } from '~/lib/utils/datetime_utility'; describe('Snippet header component', () => { let wrapper; @@ -67,7 +68,7 @@ describe('Snippet header component', () => { name: 'Thor Odinson', }, blobs: [Blob], - createdAt: new Date(Date.now() - 32 * 24 * 3600 * 1000).toISOString(), + createdAt: new Date(differenceInMilliseconds(32 * 24 * 3600 * 1000)).toISOString(), }; mutationVariables = { diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb index 5c990eb3248..308f7f46251 100644 --- a/spec/lib/gitlab/http_spec.rb +++ b/spec/lib/gitlab/http_spec.rb @@ -157,17 +157,6 @@ RSpec.describe Gitlab::HTTP do described_class.put('http://example.org', write_timeout: 1) end end - - context 'when default timeouts feature is disabled' do - it 'does not apply any defaults' do - stub_feature_flags(http_default_timeouts: false) - expect(described_class).to receive(:httparty_perform_request).with( - Net::HTTP::Get, 'http://example.org', open_timeout: 1 - ).and_call_original - - described_class.get('http://example.org', open_timeout: 1) - end - end end describe '.try_get' do diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 18eaafd9f89..52a8548dea2 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -194,13 +194,16 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ) end - it 'includes project imports usage data' do + it 'includes imports usage data' do for_defined_days_back do user = create(:user) - %w(gitlab_project gitlab github bitbucket bitbucket_server gitea git manifest).each do |type| + %w(gitlab_project gitlab github bitbucket bitbucket_server gitea git manifest fogbugz phabricator).each do |type| create(:project, import_type: type, creator_id: user.id) end + + jira_project = create(:project, creator_id: user.id) + create(:jira_import_state, :finished, project: jira_project) end expect(described_class.usage_activity_by_stage_manage({})).to include( @@ -214,6 +217,11 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do gitea: 2, git: 2, manifest: 2 + }, + issues_imported: { + jira: 2, + fogbugz: 2, + phabricator: 2 } } ) @@ -228,6 +236,11 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do gitea: 1, git: 1, manifest: 1 + }, + issues_imported: { + jira: 1, + fogbugz: 1, + phabricator: 1 } } ) @@ -1054,6 +1067,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do 'g_compliance_audit_events' => 123, 'i_compliance_credential_inventory' => 123, 'i_compliance_audit_events' => 123, + 'i_compliance_audit_events_api' => 123, 'compliance_unique_visits_for_any_target' => 543, 'compliance_unique_visits_for_any_target_monthly' => 987 } diff --git a/spec/migrations/20200811130433_create_missing_vulnerabilities_issue_links_spec.rb b/spec/migrations/20200811130433_create_missing_vulnerabilities_issue_links_spec.rb new file mode 100644 index 00000000000..a821e4a43df --- /dev/null +++ b/spec/migrations/20200811130433_create_missing_vulnerabilities_issue_links_spec.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200811130433_create_missing_vulnerabilities_issue_links.rb') + +RSpec.describe CreateMissingVulnerabilitiesIssueLinks, :migration do + let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } + let(:users) { table(:users) } + let(:user) { create_user! } + let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) } + let(:scanners) { table(:vulnerability_scanners) } + let(:scanner) { scanners.create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') } + let(:different_scanner) { scanners.create!(project_id: project.id, external_id: 'test 2', name: 'test scanner 2') } + let(:issues) { table(:issues) } + let(:issue1) { issues.create!(id: 123, project_id: project.id) } + let(:issue2) { issues.create!(id: 124, project_id: project.id) } + let(:issue3) { issues.create!(id: 125, project_id: project.id) } + let(:vulnerabilities) { table(:vulnerabilities) } + let(:vulnerabilities_findings) { table(:vulnerability_occurrences) } + let(:vulnerability_feedback) { table(:vulnerability_feedback) } + let(:vulnerability_issue_links) { table(:vulnerability_issue_links) } + let(:vulnerability_identifiers) { table(:vulnerability_identifiers) } + let(:vulnerability_identifier) { vulnerability_identifiers.create!(project_id: project.id, external_type: 'test 1', external_id: 'test 1', fingerprint: 'test 1', name: 'test 1') } + let(:different_vulnerability_identifier) { vulnerability_identifiers.create!(project_id: project.id, external_type: 'test 2', external_id: 'test 2', fingerprint: 'test 2', name: 'test 2') } + + let!(:vulnerability) do + create_vulnerability!( + project_id: project.id, + author_id: user.id + ) + end + + before do + create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + primary_identifier_id: vulnerability_identifier.id + ) + create_feedback!( + issue_id: issue1.id, + project_id: project.id, + author_id: user.id + ) + + # Create a finding with no vulnerability_id + # https://gitlab.com/gitlab-com/gl-infra/production/-/issues/2539 + create_finding!( + vulnerability_id: nil, + project_id: project.id, + scanner_id: different_scanner.id, + primary_identifier_id: different_vulnerability_identifier.id, + location_fingerprint: 'somewhereinspace', + uuid: 'test2' + ) + create_feedback!( + category: 2, + issue_id: issue2.id, + project_id: project.id, + author_id: user.id + ) + end + + context 'with no Vulnerabilities::IssueLinks present' do + it 'creates missing Vulnerabilities::IssueLinks' do + expect(vulnerability_issue_links.count).to eq(0) + + migrate! + + expect(vulnerability_issue_links.count).to eq(1) + end + end + + context 'when an Vulnerabilities::IssueLink already exists' do + before do + vulnerability_issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue1.id) + end + + it 'creates no duplicates' do + expect(vulnerability_issue_links.count).to eq(1) + + migrate! + + expect(vulnerability_issue_links.count).to eq(1) + end + end + + context 'when an Vulnerabilities::IssueLink of type created already exists' do + before do + vulnerability_issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue3.id, link_type: 2) + end + + it 'creates no duplicates' do + expect(vulnerability_issue_links.count).to eq(1) + + migrate! + + expect(vulnerability_issue_links.count).to eq(1) + end + end + + private + + def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0) + vulnerabilities.create!( + project_id: project_id, + author_id: author_id, + title: title, + severity: severity, + confidence: confidence, + report_type: report_type + ) + end + + # rubocop:disable Metrics/ParameterLists + def create_finding!( + vulnerability_id:, project_id:, scanner_id:, primary_identifier_id:, + name: "test", severity: 7, confidence: 7, report_type: 0, + project_fingerprint: '123qweasdzxc', location_fingerprint: 'test', + metadata_version: 'test', raw_metadata: 'test', uuid: 'test') + vulnerabilities_findings.create!( + vulnerability_id: vulnerability_id, + project_id: project_id, + name: name, + severity: severity, + confidence: confidence, + report_type: report_type, + project_fingerprint: project_fingerprint, + scanner_id: scanner.id, + primary_identifier_id: vulnerability_identifier.id, + location_fingerprint: location_fingerprint, + metadata_version: metadata_version, + raw_metadata: raw_metadata, + uuid: uuid + ) + end + # rubocop:enable Metrics/ParameterLists + + # project_fingerprint on Vulnerabilities::Finding is a bytea and we need to match this + def create_feedback!(issue_id:, project_id:, author_id:, feedback_type: 1, category: 0, project_fingerprint: '3132337177656173647a7863') + vulnerability_feedback.create!( + feedback_type: feedback_type, + issue_id: issue_id, + category: category, + project_fingerprint: project_fingerprint, + project_id: project_id, + author_id: author_id + ) + end + + def create_user!(name: "Example User", email: "user@example.com", user_type: nil, created_at: Time.now, confirmed_at: Time.now) + users.create!( + name: name, + email: email, + username: name, + projects_limit: 0, + user_type: user_type, + confirmed_at: confirmed_at + ) + end +end diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb index 5a4dc3ce187..a20ac823550 100644 --- a/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb @@ -7,9 +7,14 @@ RSpec.describe 'PipelineCancel' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } - let_it_be(:pipeline) { create(:ci_pipeline, :running, project: project, user: user) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) } - let(:mutation) { graphql_mutation(:pipeline_cancel, {}, 'errors') } + let(:mutation) do + variables = { + id: pipeline.to_global_id.to_s + } + graphql_mutation(:pipeline_cancel, variables, 'errors') + end let(:mutation_response) { graphql_mutation_response(:pipeline_cancel) } @@ -17,24 +22,25 @@ RSpec.describe 'PipelineCancel' do project.add_maintainer(user) end - it 'reports the service-level error' do - service = double(execute: ServiceResponse.error(message: 'Error canceling pipeline')) - allow(::Ci::CancelUserPipelinesService).to receive(:new).and_return(service) + it 'does not cancel any pipelines not owned by the current user' do + build = create(:ci_build, :running, pipeline: pipeline) post_graphql_mutation(mutation, current_user: create(:user)) - expect(mutation_response).to include('errors' => ['Error canceling pipeline']) + expect(graphql_errors).not_to be_empty + expect(build).not_to be_canceled end - it 'does not change any pipelines not owned by the current user' do - build = create(:ci_build, :running, pipeline: pipeline) + it 'returns a error if the pipline cannot be be canceled' do + build = create(:ci_build, :success, pipeline: pipeline) - post_graphql_mutation(mutation, current_user: create(:user)) + post_graphql_mutation(mutation, current_user: user) + expect(mutation_response).to include('errors' => include(eq 'Pipeline is not cancelable')) expect(build).not_to be_canceled end - it "cancels all of the current user's cancelable pipelines" do + it "cancels all cancelable builds from a pipeline" do build = create(:ci_build, :running, pipeline: pipeline) post_graphql_mutation(mutation, current_user: user) |