Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-07-11 12:10:29 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-11 12:10:29 +0300
commit871b886a1794e5baefd6b2f96caf2ac4ce5da6ca (patch)
treea92b04af5c5704314c31981ac4bb92c1489c46fd /spec
parentc0496e1078f8612b0c468430a79bd03c8102e2b9 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/config/settings_spec.rb4
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb37
-rw-r--r--spec/controllers/repositories/git_http_controller_spec.rb10
-rw-r--r--spec/experiments/application_experiment_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb47
-rw-r--r--spec/fixtures/api/schemas/slack/manifest.json164
-rw-r--r--spec/frontend/ci/reports/components/__snapshots__/grouped_issues_list_spec.js.snap26
-rw-r--r--spec/frontend/ci/reports/components/grouped_issues_list_spec.js83
-rw-r--r--spec/frontend/ci/reports/components/summary_row_spec.js63
-rw-r--r--spec/frontend/fixtures/timezones.rb2
-rw-r--r--spec/frontend/vue_shared/components/security_reports/__snapshots__/security_summary_spec.js.snap144
-rw-r--r--spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js104
-rw-r--r--spec/frontend/vue_shared/components/security_reports/help_icon_spec.js63
-rw-r--r--spec/frontend/vue_shared/components/security_reports/security_summary_spec.js33
-rw-r--r--spec/frontend/vue_shared/security_reports/mock_data.js136
-rw-r--r--spec/frontend/vue_shared/security_reports/security_reports_app_spec.js267
-rw-r--r--spec/frontend/vue_shared/security_reports/store/getters_spec.js182
-rw-r--r--spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js197
-rw-r--r--spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js84
-rw-r--r--spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js198
-rw-r--r--spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js84
-rw-r--r--spec/frontend/vue_shared/security_reports/store/utils_spec.js63
-rw-r--r--spec/frontend/vue_shared/security_reports/utils_spec.js48
-rw-r--r--spec/graphql/gitlab_schema_spec.rb2
-rw-r--r--spec/graphql/graphql_triggers_spec.rb22
-rw-r--r--spec/graphql/types/global_id_type_spec.rb6
-rw-r--r--spec/initializers/google_api_client_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/file_size_check/any_oversized_blob_spec.rb32
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder_spec.rb71
-rw-r--r--spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb42
-rw-r--r--spec/lib/gitlab/pagination/keyset/order_spec.rb40
-rw-r--r--spec/lib/slack/manifest_spec.rb96
-rw-r--r--spec/mailers/notify_spec.rb26
-rw-r--r--spec/models/milestone_spec.rb42
-rw-r--r--spec/rubocop/cop/ignored_columns_spec.rb8
-rw-r--r--spec/serializers/prometheus_alert_entity_spec.rb22
-rw-r--r--spec/services/milestones/create_service_spec.rb68
-rw-r--r--spec/services/milestones/update_service_spec.rb90
-rw-r--r--spec/views/admin/application_settings/_slack.html.haml_spec.rb42
40 files changed, 780 insertions, 1873 deletions
diff --git a/spec/config/settings_spec.rb b/spec/config/settings_spec.rb
index 55e675d5107..4639e533922 100644
--- a/spec/config/settings_spec.rb
+++ b/spec/config/settings_spec.rb
@@ -170,12 +170,12 @@ RSpec.describe Settings, feature_category: :system_access do
it 'defaults to using the encrypted_settings_key_base for the key' do
expect(Gitlab::EncryptedConfiguration).to receive(:new).with(hash_including(base_key: Gitlab::Application.secrets.encrypted_settings_key_base))
- Settings.encrypted('tmp/tests/test.enc')
+ described_class.encrypted('tmp/tests/test.enc')
end
it 'returns empty encrypted config when a key has not been set' do
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
- expect(Settings.encrypted('tmp/tests/test.enc').read).to be_empty
+ expect(described_class.encrypted('tmp/tests/test.enc').read).to be_empty
end
end
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 537424093fb..60343c822af 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -487,6 +487,43 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set
end
end
+ describe 'GET #slack_app_manifest_download', feature_category: :integrations do
+ before do
+ sign_in(admin)
+ end
+
+ subject { get :slack_app_manifest_download }
+
+ it 'downloads the GitLab for Slack app manifest' do
+ allow(Slack::Manifest).to receive(:to_h).and_return({ foo: 'bar' })
+
+ subject
+
+ expect(response.body).to eq('{"foo":"bar"}')
+ expect(response.headers['Content-Disposition']).to eq(
+ 'attachment; filename="slack_manifest.json"; filename*=UTF-8\'\'slack_manifest.json'
+ )
+ end
+ end
+
+ describe 'GET #slack_app_manifest_share', feature_category: :integrations do
+ before do
+ sign_in(admin)
+ end
+
+ subject { get :slack_app_manifest_share }
+
+ it 'redirects the user to the Slack Manifest share URL' do
+ allow(Slack::Manifest).to receive(:to_h).and_return({ foo: 'bar' })
+
+ subject
+
+ expect(response).to redirect_to(
+ "https://api.slack.com/apps?new_app=1&manifest_json=%7B%22foo%22%3A%22bar%22%7D"
+ )
+ end
+ end
+
describe 'GET #service_usage_data', feature_category: :service_ping do
before do
stub_usage_data_connections
diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb
index 276bd9b65b9..88af7d1fe45 100644
--- a/spec/controllers/repositories/git_http_controller_spec.rb
+++ b/spec/controllers/repositories/git_http_controller_spec.rb
@@ -79,7 +79,7 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
end
context 'when repository container is a project' do
- it_behaves_like Repositories::GitHttpController do
+ it_behaves_like described_class do
let(:container) { project }
let(:user) { project.first_owner }
let(:access_checker_class) { Gitlab::GitAccess }
@@ -133,7 +133,7 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
end
context 'when the user is a deploy token' do
- it_behaves_like Repositories::GitHttpController do
+ it_behaves_like described_class do
let(:container) { project }
let(:user) { create(:deploy_token, :project, projects: [project]) }
let(:access_checker_class) { Gitlab::GitAccess }
@@ -144,7 +144,7 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
end
context 'when repository container is a project wiki' do
- it_behaves_like Repositories::GitHttpController do
+ it_behaves_like described_class do
let(:container) { create(:project_wiki, :empty_repo, project: project) }
let(:user) { project.first_owner }
let(:access_checker_class) { Gitlab::GitAccessWiki }
@@ -155,7 +155,7 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
end
context 'when repository container is a personal snippet' do
- it_behaves_like Repositories::GitHttpController do
+ it_behaves_like described_class do
let(:container) { personal_snippet }
let(:user) { personal_snippet.author }
let(:access_checker_class) { Gitlab::GitAccessSnippet }
@@ -167,7 +167,7 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
end
context 'when repository container is a project snippet' do
- it_behaves_like Repositories::GitHttpController do
+ it_behaves_like described_class do
let(:container) { project_snippet }
let(:user) { project_snippet.author }
let(:access_checker_class) { Gitlab::GitAccessSnippet }
diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb
index ef8f8cbce3b..461a6390a33 100644
--- a/spec/experiments/application_experiment_spec.rb
+++ b/spec/experiments/application_experiment_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe ApplicationExperiment, :experiment, feature_category: :experiment
# _published_experiments.html.haml partial.
application_experiment.publish
- expect(ApplicationExperiment.published_experiments['namespaced/stub']).to include(
+ expect(described_class.published_experiments['namespaced/stub']).to include(
experiment: 'namespaced/stub',
excluded: false,
key: anything,
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 411ea8ea12e..55d3ad3aca3 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -8,11 +8,9 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do
include UsageDataHelpers
let_it_be(:admin) { create(:admin) }
- let(:dot_com?) { false }
context 'application setting :admin_mode is enabled', :request_store do
before do
- allow(Gitlab).to receive(:com?).and_return(dot_com?)
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
@@ -147,9 +145,7 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do
end
context 'Dormant users', feature_category: :user_management do
- context 'when Gitlab.com' do
- let(:dot_com?) { true }
-
+ context 'when Gitlab.com', :saas do
it 'does not expose the setting section' do
# NOTE: not_to have_content may have false positives for content
# that might not load instantly, so before checking that
@@ -163,8 +159,6 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do
end
context 'when not Gitlab.com' do
- let(:dot_com?) { false }
-
it 'exposes the setting section' do
expect(page).to have_content('Dormant users')
expect(page).to have_field('Deactivate dormant users after a period of inactivity')
@@ -366,9 +360,46 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do
end
context 'GitLab for Slack app settings', feature_category: :integrations do
+ let(:create_heading) { 'Create your GitLab for Slack app' }
+ let(:configure_heading) { 'Configure the app settings' }
+ let(:update_heading) { 'Update your Slack app' }
+
+ it 'has all sections' do
+ page.within('.as-slack') do
+ expect(page).to have_content(create_heading)
+ expect(page).to have_content(configure_heading)
+ expect(page).to have_content(update_heading)
+ end
+ end
+
+ context 'when GitLab.com', :saas do
+ it 'only has the configure section' do
+ page.within('.as-slack') do
+ expect(page).to have_content(configure_heading)
+
+ expect(page).not_to have_content(create_heading)
+ expect(page).not_to have_content(update_heading)
+ end
+ end
+ end
+
+ context 'when the `slack_app_self_managed` flag is disabled' do
+ before do
+ stub_feature_flags(slack_app_self_managed: false)
+ visit general_admin_application_settings_path
+ end
+
+ it 'does not display any sections' do
+ expect(page).not_to have_selector('.as-slack')
+ expect(page).not_to have_content(configure_heading)
+ expect(page).not_to have_content(create_heading)
+ expect(page).not_to have_content(update_heading)
+ end
+ end
+
it 'changes the settings' do
page.within('.as-slack') do
- check 'Enable Slack application'
+ check 'Enable GitLab for Slack app'
fill_in 'Client ID', with: 'slack_app_id'
fill_in 'Client secret', with: 'slack_app_secret'
fill_in 'Signing secret', with: 'slack_app_signing_secret'
diff --git a/spec/fixtures/api/schemas/slack/manifest.json b/spec/fixtures/api/schemas/slack/manifest.json
new file mode 100644
index 00000000000..236c5df46bc
--- /dev/null
+++ b/spec/fixtures/api/schemas/slack/manifest.json
@@ -0,0 +1,164 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "properties": {
+ "display_information": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 35
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 140
+ },
+ "background_color": {
+ "type": "string",
+ "pattern": "^#[0-9A-F]{6}$"
+ },
+ "long_description": {
+ "type": "string",
+ "maxLength": 4000
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "features": {
+ "type": "object",
+ "properties": {
+ "app_home": {
+ "type": "object",
+ "properties": {
+ "home_tab_enabled": {
+ "type": "boolean"
+ },
+ "messages_tab_enabled": {
+ "type": "boolean"
+ },
+ "messages_tab_read_only_enabled": {
+ "type": "boolean"
+ }
+ }
+ },
+ "bot_user": {
+ "type": "object",
+ "properties": {
+ "display_name": {
+ "type": "string",
+ "maxLength": 80
+ },
+ "always_online": {
+ "type": "boolean"
+ }
+ }
+ },
+ "slash_commands": {
+ "type": "array",
+ "items": [
+ {
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string",
+ "maxLength": 32
+ },
+ "url": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string",
+ "maxLength": 2000
+ },
+ "usage_hint": {
+ "type": "string",
+ "maxLength": 1000
+ },
+ "should_escape": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "command",
+ "description"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "oauth_config": {
+ "type": "object",
+ "properties": {
+ "redirect_urls": {
+ "type": "array",
+ "maxContains": 1000,
+ "items": [
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "scopes": {
+ "type": "object",
+ "properties": {
+ "bot": {
+ "type": "array",
+ "maxContains": 255
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "type": "object",
+ "properties": {
+ "event_subscriptions": {
+ "type": "object",
+ "properties": {
+ "request_url": {
+ "type": "string"
+ },
+ "bot_events": {
+ "type": "array",
+ "maxContains": 100,
+ "items": [
+ {
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ "interactivity": {
+ "type": "object",
+ "properties": {
+ "is_enabled": {
+ "type": "boolean"
+ },
+ "request_url": {
+ "type": "string"
+ },
+ "message_menu_options_url": {
+ "type": "string"
+ }
+ }
+ },
+ "org_deploy_enabled": {
+ "type": "boolean"
+ },
+ "socket_mode_enabled": {
+ "type": "boolean"
+ },
+ "token_rotation_enabled": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "required": [
+ "display_information"
+ ]
+}
diff --git a/spec/frontend/ci/reports/components/__snapshots__/grouped_issues_list_spec.js.snap b/spec/frontend/ci/reports/components/__snapshots__/grouped_issues_list_spec.js.snap
deleted file mode 100644
index 311a67a3e31..00000000000
--- a/spec/frontend/ci/reports/components/__snapshots__/grouped_issues_list_spec.js.snap
+++ /dev/null
@@ -1,26 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Grouped Issues List renders a smart virtual list with the correct props 1`] = `
-Object {
- "length": 4,
- "remain": 20,
- "rtag": "div",
- "size": 32,
- "wclass": "report-block-list",
- "wtag": "ul",
-}
-`;
-
-exports[`Grouped Issues List with data renders a report item with the correct props 1`] = `
-Object {
- "component": "CodequalityIssueBody",
- "iconComponent": "IssueStatusIcon",
- "isNew": false,
- "issue": Object {
- "name": "foo",
- },
- "showReportSectionStatusIcon": false,
- "status": "none",
- "statusIconSize": 24,
-}
-`;
diff --git a/spec/frontend/ci/reports/components/grouped_issues_list_spec.js b/spec/frontend/ci/reports/components/grouped_issues_list_spec.js
deleted file mode 100644
index 8beec220802..00000000000
--- a/spec/frontend/ci/reports/components/grouped_issues_list_spec.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import GroupedIssuesList from '~/ci/reports/components/grouped_issues_list.vue';
-import ReportItem from '~/ci/reports/components/report_item.vue';
-import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
-
-describe('Grouped Issues List', () => {
- let wrapper;
-
- const createComponent = ({ propsData = {}, stubs = {} } = {}) => {
- wrapper = shallowMount(GroupedIssuesList, {
- propsData,
- stubs,
- });
- };
-
- const findHeading = (groupName) => wrapper.find(`[data-testid="${groupName}Heading"`);
-
- it('renders a smart virtual list with the correct props', () => {
- createComponent({
- propsData: {
- resolvedIssues: [{ name: 'foo' }],
- unresolvedIssues: [{ name: 'bar' }],
- },
- stubs: {
- SmartVirtualList,
- },
- });
-
- expect(wrapper.findComponent(SmartVirtualList).props()).toMatchSnapshot();
- });
-
- describe('without data', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it.each(['unresolved', 'resolved'])('does not a render a header for %s issues', (issueName) => {
- expect(findHeading(issueName).exists()).toBe(false);
- });
-
- it.each(['resolved', 'unresolved'])('does not render report items for %s issues', () => {
- expect(wrapper.findComponent(ReportItem).exists()).toBe(false);
- });
- });
-
- describe('with data', () => {
- it.each`
- givenIssues | givenHeading | groupName
- ${[{ name: 'foo issue' }]} | ${'Foo Heading'} | ${'resolved'}
- ${[{ name: 'bar issue' }]} | ${'Bar Heading'} | ${'unresolved'}
- `('renders the heading for $groupName issues', ({ givenIssues, givenHeading, groupName }) => {
- createComponent({
- propsData: { [`${groupName}Issues`]: givenIssues, [`${groupName}Heading`]: givenHeading },
- });
-
- expect(findHeading(groupName).text()).toBe(givenHeading);
- });
-
- it.each(['resolved', 'unresolved'])('renders all %s issues', (issueName) => {
- const issues = [{ name: 'foo' }, { name: 'bar' }];
-
- createComponent({
- propsData: { [`${issueName}Issues`]: issues },
- });
-
- expect(wrapper.findAllComponents(ReportItem)).toHaveLength(issues.length);
- });
-
- it('renders a report item with the correct props', () => {
- createComponent({
- propsData: {
- resolvedIssues: [{ name: 'foo' }],
- component: 'CodequalityIssueBody',
- },
- stubs: {
- ReportItem,
- },
- });
-
- expect(wrapper.findComponent(ReportItem).props()).toMatchSnapshot();
- });
- });
-});
diff --git a/spec/frontend/ci/reports/components/summary_row_spec.js b/spec/frontend/ci/reports/components/summary_row_spec.js
deleted file mode 100644
index d4d2ac29fb1..00000000000
--- a/spec/frontend/ci/reports/components/summary_row_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { mount } from '@vue/test-utils';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import HelpPopover from '~/vue_shared/components/help_popover.vue';
-import SummaryRow from '~/ci/reports/components/summary_row.vue';
-
-describe('Summary row', () => {
- let wrapper;
-
- const summary = 'SAST detected 1 new vulnerability and 1 fixed vulnerability';
- const popoverOptions = {
- title: 'Static Application Security Testing (SAST)',
- content: '<a>Learn more about SAST</a>',
- };
- const statusIcon = 'warning';
-
- const createComponent = ({ props = {}, slots = {} } = {}) => {
- wrapper = extendedWrapper(
- mount(SummaryRow, {
- propsData: {
- summary,
- popoverOptions,
- statusIcon,
- ...props,
- },
- slots,
- }),
- );
- };
-
- const findSummary = () => wrapper.findByTestId('summary-row-description');
- const findStatusIcon = () => wrapper.findByTestId('summary-row-icon');
- const findHelpPopover = () => wrapper.findComponent(HelpPopover);
-
- it('renders provided summary', () => {
- createComponent();
- expect(findSummary().text()).toContain(summary);
- });
-
- it('renders provided icon', () => {
- createComponent();
- expect(findStatusIcon().find('[data-testid="status_warning-icon"]').exists()).toBe(true);
- });
-
- it('renders help popover if popoverOptions are provided', () => {
- createComponent();
- expect(findHelpPopover().props('options')).toEqual(popoverOptions);
- });
-
- it('does not render help popover if popoverOptions are not provided', () => {
- createComponent({ props: { popoverOptions: null } });
- expect(findHelpPopover().exists()).toBe(false);
- });
-
- describe('summary slot', () => {
- it('replaces the summary prop', () => {
- const summarySlotContent = 'Summary slot content';
- createComponent({ slots: { summary: summarySlotContent } });
-
- expect(wrapper.text()).not.toContain(summary);
- expect(findSummary().text()).toContain(summarySlotContent);
- });
- });
-});
diff --git a/spec/frontend/fixtures/timezones.rb b/spec/frontend/fixtures/timezones.rb
index 2393f4e797d..f04e647c8eb 100644
--- a/spec/frontend/fixtures/timezones.rb
+++ b/spec/frontend/fixtures/timezones.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe TimeZoneHelper, '(JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- include TimeZoneHelper
+ include described_class
let(:response) { @timezones.sort_by! { |tz| tz[:name] }.to_json }
diff --git a/spec/frontend/vue_shared/components/security_reports/__snapshots__/security_summary_spec.js.snap b/spec/frontend/vue_shared/components/security_reports/__snapshots__/security_summary_spec.js.snap
deleted file mode 100644
index 66d27b5d605..00000000000
--- a/spec/frontend/vue_shared/components/security_reports/__snapshots__/security_summary_spec.js.snap
+++ /dev/null
@@ -1,144 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`SecuritySummary component given the message {"countMessage": "%{criticalStart}0 Critical%{criticalEnd} %{highStart}1 High%{highEnd} and %{otherStart}0 Others%{otherEnd}", "critical": 0, "high": 1, "message": "Security scanning detected %{totalStart}1%{totalEnd} potential vulnerability", "other": 0, "status": "", "total": 1} interpolates correctly 1`] = `
-<span>
- Security scanning detected
- <strong>
- 1
- </strong>
- potential vulnerability
- <span
- class="gl-font-sm"
- >
- <span>
- <span
- class="gl-pl-4"
- >
-
- 0 Critical
-
- </span>
- </span>
-
- <span>
- <strong
- class="gl-text-red-600 gl-px-2"
- >
-
- 1 High
-
- </strong>
- </span>
- and
- <span>
- <span
- class="gl-px-2"
- >
-
- 0 Others
-
- </span>
- </span>
- </span>
-</span>
-`;
-
-exports[`SecuritySummary component given the message {"countMessage": "%{criticalStart}1 Critical%{criticalEnd} %{highStart}0 High%{highEnd} and %{otherStart}0 Others%{otherEnd}", "critical": 1, "high": 0, "message": "Security scanning detected %{totalStart}1%{totalEnd} potential vulnerability", "other": 0, "status": "", "total": 1} interpolates correctly 1`] = `
-<span>
- Security scanning detected
- <strong>
- 1
- </strong>
- potential vulnerability
- <span
- class="gl-font-sm"
- >
- <span>
- <strong
- class="gl-text-red-800 gl-pl-4"
- >
-
- 1 Critical
-
- </strong>
- </span>
-
- <span>
- <span
- class="gl-px-2"
- >
-
- 0 High
-
- </span>
- </span>
- and
- <span>
- <span
- class="gl-px-2"
- >
-
- 0 Others
-
- </span>
- </span>
- </span>
-</span>
-`;
-
-exports[`SecuritySummary component given the message {"countMessage": "%{criticalStart}1 Critical%{criticalEnd} %{highStart}2 High%{highEnd} and %{otherStart}0 Others%{otherEnd}", "critical": 1, "high": 2, "message": "Security scanning detected %{totalStart}3%{totalEnd} potential vulnerabilities", "other": 0, "status": "", "total": 3} interpolates correctly 1`] = `
-<span>
- Security scanning detected
- <strong>
- 3
- </strong>
- potential vulnerabilities
- <span
- class="gl-font-sm"
- >
- <span>
- <strong
- class="gl-text-red-800 gl-pl-4"
- >
-
- 1 Critical
-
- </strong>
- </span>
-
- <span>
- <strong
- class="gl-text-red-600 gl-px-2"
- >
-
- 2 High
-
- </strong>
- </span>
- and
- <span>
- <span
- class="gl-px-2"
- >
-
- 0 Others
-
- </span>
- </span>
- </span>
-</span>
-`;
-
-exports[`SecuritySummary component given the message {"message": ""} interpolates correctly 1`] = `
-<span>
-
- <!---->
-</span>
-`;
-
-exports[`SecuritySummary component given the message {"message": "foo"} interpolates correctly 1`] = `
-<span>
- foo
- <!---->
-</span>
-`;
diff --git a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js
deleted file mode 100644
index 6eebd129beb..00000000000
--- a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import {
- expectedDownloadDropdownPropsWithTitle,
- securityReportMergeRequestDownloadPathsQueryResponse,
-} from 'jest/vue_shared/security_reports/mock_data';
-import { createAlert } from '~/alert';
-import Component from '~/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue';
-import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
-import {
- REPORT_TYPE_SAST,
- REPORT_TYPE_SECRET_DETECTION,
-} from '~/vue_shared/security_reports/constants';
-import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
-
-jest.mock('~/alert');
-
-describe('Merge request artifact Download', () => {
- let wrapper;
-
- const defaultProps = {
- reportTypes: [REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION],
- targetProjectFullPath: '/path',
- mrIid: 123,
- };
-
- const createWrapper = ({ propsData, options }) => {
- wrapper = shallowMount(Component, {
- stubs: {
- SecurityReportDownloadDropdown,
- },
- propsData: {
- ...defaultProps,
- ...propsData,
- },
- ...options,
- });
- };
-
- const pendingHandler = () => new Promise(() => {});
- const successHandler = () =>
- Promise.resolve({ data: securityReportMergeRequestDownloadPathsQueryResponse });
- const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] });
- const createMockApolloProvider = (handler) => {
- Vue.use(VueApollo);
- const requestHandlers = [[securityReportMergeRequestDownloadPathsQuery, handler]];
-
- return createMockApollo(requestHandlers);
- };
-
- const findDownloadDropdown = () => wrapper.findComponent(SecurityReportDownloadDropdown);
-
- describe('given the query is loading', () => {
- beforeEach(() => {
- createWrapper({
- options: {
- apolloProvider: createMockApolloProvider(pendingHandler),
- },
- });
- });
-
- it('loading is true', () => {
- expect(findDownloadDropdown().props('loading')).toBe(true);
- });
- });
-
- describe('given the query loads successfully', () => {
- beforeEach(() => {
- createWrapper({
- options: {
- apolloProvider: createMockApolloProvider(successHandler),
- },
- });
- });
-
- it('renders the download dropdown', () => {
- expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithTitle);
- });
- });
-
- describe('given the query fails', () => {
- beforeEach(() => {
- createWrapper({
- options: {
- apolloProvider: createMockApolloProvider(failureHandler),
- },
- });
- });
-
- it('calls createAlert correctly', () => {
- expect(createAlert).toHaveBeenCalledWith({
- message: Component.i18n.apiError,
- captureError: true,
- error: expect.any(Error),
- });
- });
-
- it('renders nothing', () => {
- expect(findDownloadDropdown().props('artifacts')).toEqual([]);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js b/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js
deleted file mode 100644
index 2f6e633fb34..00000000000
--- a/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { GlLink, GlPopover } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue';
-
-const helpPath = '/docs';
-const discoverProjectSecurityPath = '/discoverProjectSecurityPath';
-
-describe('HelpIcon component', () => {
- let wrapper;
-
- const createWrapper = (props) => {
- wrapper = shallowMount(HelpIcon, {
- propsData: {
- helpPath,
- ...props,
- },
- });
- };
-
- const findLink = () => wrapper.findComponent(GlLink);
- const findPopover = () => wrapper.findComponent(GlPopover);
- const findPopoverTarget = () => wrapper.findComponent({ ref: 'discoverProjectSecurity' });
-
- describe('given a help path only', () => {
- beforeEach(() => {
- createWrapper();
- });
-
- it('does not render a popover', () => {
- expect(findPopover().exists()).toBe(false);
- });
-
- it('renders a help link', () => {
- expect(findLink().attributes()).toMatchObject({
- href: helpPath,
- target: '_blank',
- });
- });
- });
-
- describe('given a help path and discover project security path', () => {
- beforeEach(() => {
- createWrapper({ discoverProjectSecurityPath });
- });
-
- it('renders a popover', () => {
- const popover = findPopover();
- expect(popover.props('target')()).toBe(findPopoverTarget().element);
- expect(popover.attributes()).toMatchObject({
- title: HelpIcon.i18n.upgradeToManageVulnerabilities,
- triggers: 'click blur',
- });
- expect(popover.text()).toContain(HelpIcon.i18n.upgradeToInteract);
- });
-
- it('renders a link to the discover path', () => {
- expect(findLink().attributes()).toMatchObject({
- href: discoverProjectSecurityPath,
- target: '_blank',
- });
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js b/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js
deleted file mode 100644
index 61cdc329220..00000000000
--- a/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { GlSprintf } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import SecuritySummary from '~/vue_shared/security_reports/components/security_summary.vue';
-import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils';
-
-describe('SecuritySummary component', () => {
- let wrapper;
-
- const createWrapper = (message) => {
- wrapper = shallowMount(SecuritySummary, {
- propsData: { message },
- stubs: {
- GlSprintf,
- },
- });
- };
-
- describe.each([
- { message: '' },
- { message: 'foo' },
- groupedTextBuilder({ reportType: 'Security scanning', critical: 1, high: 0, total: 1 }),
- groupedTextBuilder({ reportType: 'Security scanning', critical: 0, high: 1, total: 1 }),
- groupedTextBuilder({ reportType: 'Security scanning', critical: 1, high: 2, total: 3 }),
- ])('given the message %p', (message) => {
- beforeEach(() => {
- createWrapper(message);
- });
-
- it('interpolates correctly', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-});
diff --git a/spec/frontend/vue_shared/security_reports/mock_data.js b/spec/frontend/vue_shared/security_reports/mock_data.js
index a9ad675e538..533d312a4de 100644
--- a/spec/frontend/vue_shared/security_reports/mock_data.js
+++ b/spec/frontend/vue_shared/security_reports/mock_data.js
@@ -341,120 +341,6 @@ export const securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse = {
},
};
-export const securityReportMergeRequestDownloadPathsQueryResponse = {
- project: {
- id: '1',
- mergeRequest: {
- id: 'mr-1',
- headPipeline: {
- id: 'gid://gitlab/Ci::Pipeline/176',
- jobs: {
- nodes: [
- {
- id: 'job-1',
- name: 'secret_detection',
- artifacts: {
- nodes: [
- {
- downloadPath:
- '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=trace',
- fileType: 'TRACE',
- __typename: 'CiJobArtifact',
- },
- {
- downloadPath:
- '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=secret_detection',
- fileType: 'SECRET_DETECTION',
- __typename: 'CiJobArtifact',
- },
- ],
- __typename: 'CiJobArtifactConnection',
- },
- __typename: 'CiJob',
- },
- {
- id: 'job-2',
- name: 'bandit-sast',
- artifacts: {
- nodes: [
- {
- downloadPath:
- '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=trace',
- fileType: 'TRACE',
- __typename: 'CiJobArtifact',
- },
- {
- downloadPath:
- '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=sast',
- fileType: 'SAST',
- __typename: 'CiJobArtifact',
- },
- ],
- __typename: 'CiJobArtifactConnection',
- },
- __typename: 'CiJob',
- },
- {
- id: 'job-3',
- name: 'eslint-sast',
- artifacts: {
- nodes: [
- {
- downloadPath:
- '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=trace',
- fileType: 'TRACE',
- __typename: 'CiJobArtifact',
- },
- {
- downloadPath:
- '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=sast',
- fileType: 'SAST',
- __typename: 'CiJobArtifact',
- },
- ],
- __typename: 'CiJobArtifactConnection',
- },
- __typename: 'CiJob',
- },
- {
- id: 'job-4',
- name: 'all_artifacts',
- artifacts: {
- nodes: [
- {
- downloadPath:
- '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=archive',
- fileType: 'ARCHIVE',
- __typename: 'CiJobArtifact',
- },
- {
- downloadPath:
- '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=trace',
- fileType: 'TRACE',
- __typename: 'CiJobArtifact',
- },
- {
- downloadPath:
- '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=metadata',
- fileType: 'METADATA',
- __typename: 'CiJobArtifact',
- },
- ],
- __typename: 'CiJobArtifactConnection',
- },
- __typename: 'CiJob',
- },
- ],
- __typename: 'CiJobConnection',
- },
- __typename: 'Pipeline',
- },
- __typename: 'MergeRequest',
- },
- __typename: 'Project',
- },
-};
-
export const securityReportPipelineDownloadPathsQueryResponse = {
project: {
id: 'project-1',
@@ -566,9 +452,6 @@ export const securityReportPipelineDownloadPathsQueryResponse = {
__typename: 'Project',
};
-/**
- * These correspond to SAST jobs in the securityReportMergeRequestDownloadPathsQueryResponse above.
- */
export const sastArtifacts = [
{
name: 'bandit-sast',
@@ -582,9 +465,6 @@ export const sastArtifacts = [
},
];
-/**
- * These correspond to Secret Detection jobs in the securityReportMergeRequestDownloadPathsQueryResponse above.
- */
export const secretDetectionArtifacts = [
{
name: 'secret_detection',
@@ -594,13 +474,6 @@ export const secretDetectionArtifacts = [
},
];
-export const expectedDownloadDropdownPropsWithTitle = {
- loading: false,
- artifacts: [...secretDetectionArtifacts, ...sastArtifacts],
- text: '',
- title: 'Download results',
-};
-
export const expectedDownloadDropdownPropsWithText = {
loading: false,
artifacts: [...secretDetectionArtifacts, ...sastArtifacts],
@@ -608,9 +481,6 @@ export const expectedDownloadDropdownPropsWithText = {
text: 'Download results',
};
-/**
- * These correspond to any jobs with zip archives in the securityReportMergeRequestDownloadPathsQueryResponse above.
- */
export const archiveArtifacts = [
{
name: 'all_artifacts Archive',
@@ -619,9 +489,6 @@ export const archiveArtifacts = [
},
];
-/**
- * These correspond to any jobs with trace data in the securityReportMergeRequestDownloadPathsQueryResponse above.
- */
export const traceArtifacts = [
{
name: 'secret_detection Trace',
@@ -645,9 +512,6 @@ export const traceArtifacts = [
},
];
-/**
- * These correspond to any jobs with metadata data in the securityReportMergeRequestDownloadPathsQueryResponse above.
- */
export const metadataArtifacts = [
{
name: 'all_artifacts Metadata',
diff --git a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js
deleted file mode 100644
index 257f59612e8..00000000000
--- a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js
+++ /dev/null
@@ -1,267 +0,0 @@
-import { mount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
-import { merge } from 'lodash';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import Vuex from 'vuex';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import { trimText } from 'helpers/text_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import {
- expectedDownloadDropdownPropsWithText,
- securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse,
- securityReportMergeRequestDownloadPathsQueryResponse,
- sastDiffSuccessMock,
- secretDetectionDiffSuccessMock,
-} from 'jest/vue_shared/security_reports/mock_data';
-import { createAlert } from '~/alert';
-import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue';
-import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
-import {
- REPORT_TYPE_SAST,
- REPORT_TYPE_SECRET_DETECTION,
-} from '~/vue_shared/security_reports/constants';
-import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
-import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue';
-
-jest.mock('~/alert');
-
-Vue.use(VueApollo);
-Vue.use(Vuex);
-
-const SAST_COMPARISON_PATH = '/sast.json';
-const SECRET_DETECTION_COMPARISON_PATH = '/secret_detection.json';
-
-describe('Security reports app', () => {
- let wrapper;
-
- const props = {
- pipelineId: 123,
- projectId: 456,
- securityReportsDocsPath: '/docs',
- discoverProjectSecurityPath: '/discoverProjectSecurityPath',
- };
-
- const createComponent = (options) => {
- wrapper = mount(
- SecurityReportsApp,
- merge(
- {
- propsData: { ...props },
- stubs: {
- HelpIcon: true,
- },
- },
- options,
- ),
- );
- };
-
- const pendingHandler = () => new Promise(() => {});
- const successHandler = () =>
- Promise.resolve({ data: securityReportMergeRequestDownloadPathsQueryResponse });
- const successEmptyHandler = () =>
- Promise.resolve({ data: securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse });
- const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] });
- const createMockApolloProvider = (handler) => {
- const requestHandlers = [[securityReportMergeRequestDownloadPathsQuery, handler]];
-
- return createMockApollo(requestHandlers);
- };
-
- const findDownloadDropdown = () => wrapper.findComponent(SecurityReportDownloadDropdown);
- const findHelpIconComponent = () => wrapper.findComponent(HelpIcon);
-
- describe('given the artifacts query is loading', () => {
- beforeEach(() => {
- createComponent({
- apolloProvider: createMockApolloProvider(pendingHandler),
- });
- });
-
- // TODO: Remove this assertion as part of
- // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
- it('initially renders nothing', () => {
- expect(wrapper.html()).toBe('');
- });
- });
-
- describe('given the artifacts query loads successfully', () => {
- beforeEach(() => {
- createComponent({
- apolloProvider: createMockApolloProvider(successHandler),
- });
- });
-
- it('renders the download dropdown', () => {
- expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithText);
- });
-
- it('renders the expected message', () => {
- expect(wrapper.text()).toContain(SecurityReportsApp.i18n.scansHaveRun);
- });
-
- it('renders a help link', () => {
- expect(findHelpIconComponent().props()).toEqual({
- helpPath: props.securityReportsDocsPath,
- discoverProjectSecurityPath: props.discoverProjectSecurityPath,
- });
- });
- });
-
- describe('given the artifacts query loads successfully with no artifacts', () => {
- beforeEach(() => {
- createComponent({
- apolloProvider: createMockApolloProvider(successEmptyHandler),
- });
- });
-
- // TODO: Remove this assertion as part of
- // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
- it('initially renders nothing', () => {
- expect(wrapper.html()).toBe('');
- });
- });
-
- describe('given the artifacts query fails', () => {
- beforeEach(() => {
- createComponent({
- apolloProvider: createMockApolloProvider(failureHandler),
- });
- });
-
- it('calls createAlert correctly', () => {
- expect(createAlert).toHaveBeenCalledWith({
- message: SecurityReportsApp.i18n.apiError,
- captureError: true,
- error: expect.any(Error),
- });
- });
-
- // TODO: Remove this assertion as part of
- // https://gitlab.com/gitlab-org/gitlab/-/issues/273431
- it('renders nothing', () => {
- expect(wrapper.html()).toBe('');
- });
- });
-
- describe('given the coreSecurityMrWidgetCounts feature flag is enabled', () => {
- let mock;
-
- const createComponentWithFlagEnabled = (options) =>
- createComponent(
- merge(options, {
- provide: {
- glFeatures: {
- coreSecurityMrWidgetCounts: true,
- },
- },
- apolloProvider: createMockApolloProvider(successHandler),
- }),
- );
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- const SAST_SUCCESS_MESSAGE =
- 'Security scanning detected 1 potential vulnerability 1 Critical 0 High and 0 Others';
- const SECRET_DETECTION_SUCCESS_MESSAGE =
- 'Security scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others';
- describe.each`
- reportType | pathProp | path | successResponse | successMessage
- ${REPORT_TYPE_SAST} | ${'sastComparisonPath'} | ${SAST_COMPARISON_PATH} | ${sastDiffSuccessMock} | ${SAST_SUCCESS_MESSAGE}
- ${REPORT_TYPE_SECRET_DETECTION} | ${'secretDetectionComparisonPath'} | ${SECRET_DETECTION_COMPARISON_PATH} | ${secretDetectionDiffSuccessMock} | ${SECRET_DETECTION_SUCCESS_MESSAGE}
- `(
- 'given a $pathProp and $reportType artifact',
- ({ pathProp, path, successResponse, successMessage }) => {
- describe('when loading', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios, { delayResponse: 1 });
- mock.onGet(path).replyOnce(HTTP_STATUS_OK, successResponse);
-
- createComponentWithFlagEnabled({
- propsData: {
- [pathProp]: path,
- },
- });
-
- return waitForPromises();
- });
-
- it('should have loading message', () => {
- expect(wrapper.text()).toContain('Security scanning is loading');
- });
-
- it('renders the download dropdown', () => {
- expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithText);
- });
- });
-
- describe('when successfully loaded', () => {
- beforeEach(() => {
- mock.onGet(path).replyOnce(HTTP_STATUS_OK, successResponse);
-
- createComponentWithFlagEnabled({
- propsData: {
- [pathProp]: path,
- },
- });
-
- return waitForPromises();
- });
-
- it('should show counts', () => {
- expect(trimText(wrapper.text())).toContain(successMessage);
- });
-
- it('renders the download dropdown', () => {
- expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithText);
- });
- });
-
- describe('when an error occurs', () => {
- beforeEach(() => {
- mock.onGet(path).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
-
- createComponentWithFlagEnabled({
- propsData: {
- [pathProp]: path,
- },
- });
-
- return waitForPromises();
- });
-
- it('should show error message', () => {
- expect(trimText(wrapper.text())).toContain('Loading resulted in an error');
- });
-
- it('renders the download dropdown', () => {
- expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithText);
- });
- });
-
- describe('when the comparison endpoint is not provided', () => {
- beforeEach(() => {
- mock.onGet(path).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
-
- createComponentWithFlagEnabled();
-
- return waitForPromises();
- });
-
- it('renders the basic scansHaveRun message', () => {
- expect(wrapper.text()).toContain(SecurityReportsApp.i18n.scansHaveRun);
- });
- });
- },
- );
- });
-});
diff --git a/spec/frontend/vue_shared/security_reports/store/getters_spec.js b/spec/frontend/vue_shared/security_reports/store/getters_spec.js
deleted file mode 100644
index bcc8955ba02..00000000000
--- a/spec/frontend/vue_shared/security_reports/store/getters_spec.js
+++ /dev/null
@@ -1,182 +0,0 @@
-import {
- groupedSummaryText,
- allReportsHaveError,
- areReportsLoading,
- anyReportHasError,
- areAllReportsLoading,
- anyReportHasIssues,
- summaryCounts,
-} from '~/vue_shared/security_reports/store/getters';
-import createSastState from '~/vue_shared/security_reports/store/modules/sast/state';
-import createSecretDetectionState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
-import createState from '~/vue_shared/security_reports/store/state';
-import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils';
-import { CRITICAL, HIGH, LOW } from '~/vulnerabilities/constants';
-
-const generateVuln = (severity) => ({ severity });
-
-describe('Security reports getters', () => {
- let state;
-
- beforeEach(() => {
- state = createState();
- state.sast = createSastState();
- state.secretDetection = createSecretDetectionState();
- });
-
- describe('summaryCounts', () => {
- it('returns 0 count for empty state', () => {
- expect(summaryCounts(state)).toEqual({
- critical: 0,
- high: 0,
- other: 0,
- });
- });
-
- describe('combines all reports', () => {
- it('of the same severity', () => {
- state.sast.newIssues = [generateVuln(CRITICAL)];
- state.secretDetection.newIssues = [generateVuln(CRITICAL)];
-
- expect(summaryCounts(state)).toEqual({
- critical: 2,
- high: 0,
- other: 0,
- });
- });
-
- it('of different severities', () => {
- state.sast.newIssues = [generateVuln(CRITICAL)];
- state.secretDetection.newIssues = [generateVuln(HIGH), generateVuln(LOW)];
-
- expect(summaryCounts(state)).toEqual({
- critical: 1,
- high: 1,
- other: 1,
- });
- });
- });
- });
-
- describe('groupedSummaryText', () => {
- it('returns failed text', () => {
- expect(
- groupedSummaryText(state, {
- allReportsHaveError: true,
- areReportsLoading: false,
- summaryCounts: {},
- }),
- ).toEqual({ message: 'Security scanning failed loading any results' });
- });
-
- it('returns `is loading` as status text', () => {
- expect(
- groupedSummaryText(state, {
- allReportsHaveError: false,
- areReportsLoading: true,
- summaryCounts: {},
- }),
- ).toEqual(
- groupedTextBuilder({
- reportType: 'Security scanning',
- critical: 0,
- high: 0,
- other: 0,
- status: 'is loading',
- }),
- );
- });
-
- it('returns no new status text if there are existing ones', () => {
- expect(
- groupedSummaryText(state, {
- allReportsHaveError: false,
- areReportsLoading: false,
- summaryCounts: {},
- }),
- ).toEqual(
- groupedTextBuilder({
- reportType: 'Security scanning',
- critical: 0,
- high: 0,
- other: 0,
- status: '',
- }),
- );
- });
- });
-
- describe('areReportsLoading', () => {
- it('returns true when any report is loading', () => {
- state.sast.isLoading = true;
-
- expect(areReportsLoading(state)).toEqual(true);
- });
-
- it('returns false when none of the reports are loading', () => {
- expect(areReportsLoading(state)).toEqual(false);
- });
- });
-
- describe('areAllReportsLoading', () => {
- it('returns true when all reports are loading', () => {
- state.sast.isLoading = true;
- state.secretDetection.isLoading = true;
-
- expect(areAllReportsLoading(state)).toEqual(true);
- });
-
- it('returns false when some of the reports are loading', () => {
- state.sast.isLoading = true;
-
- expect(areAllReportsLoading(state)).toEqual(false);
- });
-
- it('returns false when none of the reports are loading', () => {
- expect(areAllReportsLoading(state)).toEqual(false);
- });
- });
-
- describe('allReportsHaveError', () => {
- it('returns true when all reports have error', () => {
- state.sast.hasError = true;
- state.secretDetection.hasError = true;
-
- expect(allReportsHaveError(state)).toEqual(true);
- });
-
- it('returns false when none of the reports have error', () => {
- expect(allReportsHaveError(state)).toEqual(false);
- });
-
- it('returns false when one of the reports does not have error', () => {
- state.secretDetection.hasError = true;
-
- expect(allReportsHaveError(state)).toEqual(false);
- });
- });
-
- describe('anyReportHasError', () => {
- it('returns true when any of the reports has error', () => {
- state.sast.hasError = true;
-
- expect(anyReportHasError(state)).toEqual(true);
- });
-
- it('returns false when none of the reports has error', () => {
- expect(anyReportHasError(state)).toEqual(false);
- });
- });
-
- describe('anyReportHasIssues', () => {
- it('returns true when any of the reports has new issues', () => {
- state.sast.newIssues.push(generateVuln(LOW));
-
- expect(anyReportHasIssues(state)).toEqual(true);
- });
-
- it('returns false when none of the reports has error', () => {
- expect(anyReportHasIssues(state)).toEqual(false);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js
deleted file mode 100644
index 0cab950cb77..00000000000
--- a/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js
+++ /dev/null
@@ -1,197 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
-
-import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-import * as actions from '~/vue_shared/security_reports/store/modules/sast/actions';
-import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types';
-import createState from '~/vue_shared/security_reports/store/modules/sast/state';
-
-const diffEndpoint = 'diff-endpoint.json';
-const blobPath = 'blob-path.json';
-const reports = {
- base: 'base',
- head: 'head',
- enrichData: 'enrichData',
- diff: 'diff',
-};
-const error = 'Something went wrong';
-const vulnerabilityFeedbackPath = 'vulnerability-feedback-path';
-const rootState = { vulnerabilityFeedbackPath, blobPath };
-
-let state;
-
-describe('sast report actions', () => {
- beforeEach(() => {
- state = createState();
- });
-
- describe('setDiffEndpoint', () => {
- it(`should commit ${types.SET_DIFF_ENDPOINT} with the correct path`, () => {
- return testAction(
- actions.setDiffEndpoint,
- diffEndpoint,
- state,
- [
- {
- type: types.SET_DIFF_ENDPOINT,
- payload: diffEndpoint,
- },
- ],
- [],
- );
- });
- });
-
- describe('requestDiff', () => {
- it(`should commit ${types.REQUEST_DIFF}`, () => {
- return testAction(actions.requestDiff, {}, state, [{ type: types.REQUEST_DIFF }], []);
- });
- });
-
- describe('receiveDiffSuccess', () => {
- it(`should commit ${types.RECEIVE_DIFF_SUCCESS} with the correct response`, () => {
- return testAction(
- actions.receiveDiffSuccess,
- reports,
- state,
- [
- {
- type: types.RECEIVE_DIFF_SUCCESS,
- payload: reports,
- },
- ],
- [],
- );
- });
- });
-
- describe('receiveDiffError', () => {
- it(`should commit ${types.RECEIVE_DIFF_ERROR} with the correct response`, () => {
- return testAction(
- actions.receiveDiffError,
- error,
- state,
- [
- {
- type: types.RECEIVE_DIFF_ERROR,
- payload: error,
- },
- ],
- [],
- );
- });
- });
-
- describe('fetchDiff', () => {
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- state.paths.diffEndpoint = diffEndpoint;
- rootState.canReadVulnerabilityFeedback = true;
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('when diff and vulnerability feedback endpoints respond successfully', () => {
- beforeEach(() => {
- mock
- .onGet(diffEndpoint)
- .replyOnce(HTTP_STATUS_OK, reports.diff)
- .onGet(vulnerabilityFeedbackPath)
- .replyOnce(HTTP_STATUS_OK, reports.enrichData);
- });
-
- it('should dispatch the `receiveDiffSuccess` action', () => {
- const { diff, enrichData } = reports;
- return testAction(
- actions.fetchDiff,
- {},
- { ...rootState, ...state },
- [],
- [
- { type: 'requestDiff' },
- {
- type: 'receiveDiffSuccess',
- payload: {
- diff,
- enrichData,
- },
- },
- ],
- );
- });
- });
-
- describe('when diff endpoint responds successfully and fetching vulnerability feedback is not authorized', () => {
- beforeEach(() => {
- rootState.canReadVulnerabilityFeedback = false;
- mock.onGet(diffEndpoint).replyOnce(HTTP_STATUS_OK, reports.diff);
- });
-
- it('should dispatch the `receiveDiffSuccess` action with empty enrich data', () => {
- const { diff } = reports;
- const enrichData = [];
- return testAction(
- actions.fetchDiff,
- {},
- { ...rootState, ...state },
- [],
- [
- { type: 'requestDiff' },
- {
- type: 'receiveDiffSuccess',
- payload: {
- diff,
- enrichData,
- },
- },
- ],
- );
- });
- });
-
- describe('when the vulnerability feedback endpoint fails', () => {
- beforeEach(() => {
- mock
- .onGet(diffEndpoint)
- .replyOnce(HTTP_STATUS_OK, reports.diff)
- .onGet(vulnerabilityFeedbackPath)
- .replyOnce(HTTP_STATUS_NOT_FOUND);
- });
-
- it('should dispatch the `receiveError` action', () => {
- return testAction(
- actions.fetchDiff,
- {},
- { ...rootState, ...state },
- [],
- [{ type: 'requestDiff' }, { type: 'receiveDiffError' }],
- );
- });
- });
-
- describe('when the diff endpoint fails', () => {
- beforeEach(() => {
- mock
- .onGet(diffEndpoint)
- .replyOnce(HTTP_STATUS_NOT_FOUND)
- .onGet(vulnerabilityFeedbackPath)
- .replyOnce(HTTP_STATUS_OK, reports.enrichData);
- });
-
- it('should dispatch the `receiveDiffError` action', () => {
- return testAction(
- actions.fetchDiff,
- {},
- { ...rootState, ...state },
- [],
- [{ type: 'requestDiff' }, { type: 'receiveDiffError' }],
- );
- });
- });
- });
-});
diff --git a/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js
deleted file mode 100644
index d6119f44619..00000000000
--- a/spec/frontend/vue_shared/security_reports/store/modules/sast/mutations_spec.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types';
-import mutations from '~/vue_shared/security_reports/store/modules/sast/mutations';
-import createState from '~/vue_shared/security_reports/store/modules/sast/state';
-
-const createIssue = ({ ...config }) => ({ changed: false, ...config });
-
-describe('sast module mutations', () => {
- const path = 'path';
- let state;
-
- beforeEach(() => {
- state = createState();
- });
-
- describe(types.SET_DIFF_ENDPOINT, () => {
- it('should set the SAST diff endpoint', () => {
- mutations[types.SET_DIFF_ENDPOINT](state, path);
-
- expect(state.paths.diffEndpoint).toBe(path);
- });
- });
-
- describe(types.REQUEST_DIFF, () => {
- it('should set the `isLoading` status to `true`', () => {
- mutations[types.REQUEST_DIFF](state);
-
- expect(state.isLoading).toBe(true);
- });
- });
-
- describe(types.RECEIVE_DIFF_SUCCESS, () => {
- beforeEach(() => {
- const reports = {
- diff: {
- added: [
- createIssue({ cve: 'CVE-1' }),
- createIssue({ cve: 'CVE-2' }),
- createIssue({ cve: 'CVE-3' }),
- ],
- fixed: [createIssue({ cve: 'CVE-4' }), createIssue({ cve: 'CVE-5' })],
- existing: [createIssue({ cve: 'CVE-6' })],
- base_report_out_of_date: true,
- },
- };
- state.isLoading = true;
- mutations[types.RECEIVE_DIFF_SUCCESS](state, reports);
- });
-
- it('should set the `isLoading` status to `false`', () => {
- expect(state.isLoading).toBe(false);
- });
-
- it('should set the `baseReportOutofDate` status to `false`', () => {
- expect(state.baseReportOutofDate).toBe(true);
- });
-
- it('should have the relevant `new` issues', () => {
- expect(state.newIssues).toHaveLength(3);
- });
-
- it('should have the relevant `resolved` issues', () => {
- expect(state.resolvedIssues).toHaveLength(2);
- });
-
- it('should have the relevant `all` issues', () => {
- expect(state.allIssues).toHaveLength(1);
- });
- });
-
- describe(types.RECEIVE_DIFF_ERROR, () => {
- beforeEach(() => {
- state.isLoading = true;
- mutations[types.RECEIVE_DIFF_ERROR](state);
- });
-
- it('should set the `isLoading` status to `false`', () => {
- expect(state.isLoading).toBe(false);
- });
-
- it('should set the `hasError` status to `true`', () => {
- expect(state.hasError).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js
deleted file mode 100644
index 7197784c3e8..00000000000
--- a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js
+++ /dev/null
@@ -1,198 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
-
-import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-import * as actions from '~/vue_shared/security_reports/store/modules/secret_detection/actions';
-import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
-import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
-
-const diffEndpoint = 'diff-endpoint.json';
-const blobPath = 'blob-path.json';
-const reports = {
- base: 'base',
- head: 'head',
- enrichData: 'enrichData',
- diff: 'diff',
-};
-const error = 'Something went wrong';
-const vulnerabilityFeedbackPath = 'vulnerability-feedback-path';
-const rootState = { vulnerabilityFeedbackPath, blobPath };
-
-let state;
-
-describe('secret detection report actions', () => {
- beforeEach(() => {
- state = createState();
- });
-
- describe('setDiffEndpoint', () => {
- it(`should commit ${types.SET_DIFF_ENDPOINT} with the correct path`, () => {
- return testAction(
- actions.setDiffEndpoint,
- diffEndpoint,
- state,
- [
- {
- type: types.SET_DIFF_ENDPOINT,
- payload: diffEndpoint,
- },
- ],
- [],
- );
- });
- });
-
- describe('requestDiff', () => {
- it(`should commit ${types.REQUEST_DIFF}`, () => {
- return testAction(actions.requestDiff, {}, state, [{ type: types.REQUEST_DIFF }], []);
- });
- });
-
- describe('receiveDiffSuccess', () => {
- it(`should commit ${types.RECEIVE_DIFF_SUCCESS} with the correct response`, () => {
- return testAction(
- actions.receiveDiffSuccess,
- reports,
- state,
- [
- {
- type: types.RECEIVE_DIFF_SUCCESS,
- payload: reports,
- },
- ],
- [],
- );
- });
- });
-
- describe('receiveDiffError', () => {
- it(`should commit ${types.RECEIVE_DIFF_ERROR} with the correct response`, () => {
- return testAction(
- actions.receiveDiffError,
- error,
- state,
- [
- {
- type: types.RECEIVE_DIFF_ERROR,
- payload: error,
- },
- ],
- [],
- );
- });
- });
-
- describe('fetchDiff', () => {
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- state.paths.diffEndpoint = diffEndpoint;
- rootState.canReadVulnerabilityFeedback = true;
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('when diff and vulnerability feedback endpoints respond successfully', () => {
- beforeEach(() => {
- mock
- .onGet(diffEndpoint)
- .replyOnce(HTTP_STATUS_OK, reports.diff)
- .onGet(vulnerabilityFeedbackPath)
- .replyOnce(HTTP_STATUS_OK, reports.enrichData);
- });
-
- it('should dispatch the `receiveDiffSuccess` action', () => {
- const { diff, enrichData } = reports;
-
- return testAction(
- actions.fetchDiff,
- {},
- { ...rootState, ...state },
- [],
- [
- { type: 'requestDiff' },
- {
- type: 'receiveDiffSuccess',
- payload: {
- diff,
- enrichData,
- },
- },
- ],
- );
- });
- });
-
- describe('when diff endpoint responds successfully and fetching vulnerability feedback is not authorized', () => {
- beforeEach(() => {
- rootState.canReadVulnerabilityFeedback = false;
- mock.onGet(diffEndpoint).replyOnce(HTTP_STATUS_OK, reports.diff);
- });
-
- it('should dispatch the `receiveDiffSuccess` action with empty enrich data', () => {
- const { diff } = reports;
- const enrichData = [];
- return testAction(
- actions.fetchDiff,
- {},
- { ...rootState, ...state },
- [],
- [
- { type: 'requestDiff' },
- {
- type: 'receiveDiffSuccess',
- payload: {
- diff,
- enrichData,
- },
- },
- ],
- );
- });
- });
-
- describe('when the vulnerability feedback endpoint fails', () => {
- beforeEach(() => {
- mock
- .onGet(diffEndpoint)
- .replyOnce(HTTP_STATUS_OK, reports.diff)
- .onGet(vulnerabilityFeedbackPath)
- .replyOnce(HTTP_STATUS_NOT_FOUND);
- });
-
- it('should dispatch the `receiveDiffError` action', () => {
- return testAction(
- actions.fetchDiff,
- {},
- { ...rootState, ...state },
- [],
- [{ type: 'requestDiff' }, { type: 'receiveDiffError' }],
- );
- });
- });
-
- describe('when the diff endpoint fails', () => {
- beforeEach(() => {
- mock
- .onGet(diffEndpoint)
- .replyOnce(HTTP_STATUS_NOT_FOUND)
- .onGet(vulnerabilityFeedbackPath)
- .replyOnce(HTTP_STATUS_OK, reports.enrichData);
- });
-
- it('should dispatch the `receiveDiffError` action', () => {
- return testAction(
- actions.fetchDiff,
- {},
- { ...rootState, ...state },
- [],
- [{ type: 'requestDiff' }, { type: 'receiveDiffError' }],
- );
- });
- });
- });
-});
diff --git a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js
deleted file mode 100644
index 42da7476a40..00000000000
--- a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/mutations_spec.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types';
-import mutations from '~/vue_shared/security_reports/store/modules/secret_detection/mutations';
-import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state';
-
-const createIssue = ({ ...config }) => ({ changed: false, ...config });
-
-describe('secret detection module mutations', () => {
- const path = 'path';
- let state;
-
- beforeEach(() => {
- state = createState();
- });
-
- describe(types.SET_DIFF_ENDPOINT, () => {
- it('should set the secret detection diff endpoint', () => {
- mutations[types.SET_DIFF_ENDPOINT](state, path);
-
- expect(state.paths.diffEndpoint).toBe(path);
- });
- });
-
- describe(types.REQUEST_DIFF, () => {
- it('should set the `isLoading` status to `true`', () => {
- mutations[types.REQUEST_DIFF](state);
-
- expect(state.isLoading).toBe(true);
- });
- });
-
- describe(types.RECEIVE_DIFF_SUCCESS, () => {
- beforeEach(() => {
- const reports = {
- diff: {
- added: [
- createIssue({ cve: 'CVE-1' }),
- createIssue({ cve: 'CVE-2' }),
- createIssue({ cve: 'CVE-3' }),
- ],
- fixed: [createIssue({ cve: 'CVE-4' }), createIssue({ cve: 'CVE-5' })],
- existing: [createIssue({ cve: 'CVE-6' })],
- base_report_out_of_date: true,
- },
- };
- state.isLoading = true;
- mutations[types.RECEIVE_DIFF_SUCCESS](state, reports);
- });
-
- it('should set the `isLoading` status to `false`', () => {
- expect(state.isLoading).toBe(false);
- });
-
- it('should set the `baseReportOutofDate` status to `true`', () => {
- expect(state.baseReportOutofDate).toBe(true);
- });
-
- it('should have the relevant `new` issues', () => {
- expect(state.newIssues).toHaveLength(3);
- });
-
- it('should have the relevant `resolved` issues', () => {
- expect(state.resolvedIssues).toHaveLength(2);
- });
-
- it('should have the relevant `all` issues', () => {
- expect(state.allIssues).toHaveLength(1);
- });
- });
-
- describe(types.RECEIVE_DIFF_ERROR, () => {
- beforeEach(() => {
- state.isLoading = true;
- mutations[types.RECEIVE_DIFF_ERROR](state);
- });
-
- it('should set the `isLoading` status to `false`', () => {
- expect(state.isLoading).toBe(false);
- });
-
- it('should set the `hasError` status to `true`', () => {
- expect(state.hasError).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/security_reports/store/utils_spec.js b/spec/frontend/vue_shared/security_reports/store/utils_spec.js
deleted file mode 100644
index c8750cd58a0..00000000000
--- a/spec/frontend/vue_shared/security_reports/store/utils_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { enrichVulnerabilityWithFeedback } from '~/vue_shared/security_reports/store/utils';
-import {
- FEEDBACK_TYPE_DISMISSAL,
- FEEDBACK_TYPE_ISSUE,
- FEEDBACK_TYPE_MERGE_REQUEST,
-} from '~/vue_shared/security_reports/constants';
-
-describe('security reports store utils', () => {
- const vulnerability = { uuid: 1 };
-
- describe('enrichVulnerabilityWithFeedback', () => {
- const dismissalFeedback = {
- feedback_type: FEEDBACK_TYPE_DISMISSAL,
- finding_uuid: vulnerability.uuid,
- };
- const dismissalVuln = { ...vulnerability, isDismissed: true, dismissalFeedback };
-
- const issueFeedback = {
- feedback_type: FEEDBACK_TYPE_ISSUE,
- issue_iid: 1,
- finding_uuid: vulnerability.uuid,
- };
- const issueVuln = { ...vulnerability, hasIssue: true, issue_feedback: issueFeedback };
- const mrFeedback = {
- feedback_type: FEEDBACK_TYPE_MERGE_REQUEST,
- merge_request_iid: 1,
- finding_uuid: vulnerability.uuid,
- };
- const mrVuln = {
- ...vulnerability,
- hasMergeRequest: true,
- merge_request_feedback: mrFeedback,
- };
-
- it.each`
- feedbacks | expected
- ${[dismissalFeedback]} | ${dismissalVuln}
- ${[{ ...issueFeedback, issue_iid: null }]} | ${vulnerability}
- ${[issueFeedback]} | ${issueVuln}
- ${[{ ...mrFeedback, merge_request_iid: null }]} | ${vulnerability}
- ${[mrFeedback]} | ${mrVuln}
- ${[dismissalFeedback, issueFeedback, mrFeedback]} | ${{ ...dismissalVuln, ...issueVuln, ...mrVuln }}
- `('returns expected enriched vulnerability: $expected', ({ feedbacks, expected }) => {
- const enrichedVulnerability = enrichVulnerabilityWithFeedback(vulnerability, feedbacks);
-
- expect(enrichedVulnerability).toEqual(expected);
- });
-
- it('matches correct feedback objects to vulnerability', () => {
- const feedbacks = [
- dismissalFeedback,
- issueFeedback,
- mrFeedback,
- { ...dismissalFeedback, finding_uuid: 2 },
- { ...issueFeedback, finding_uuid: 2 },
- { ...mrFeedback, finding_uuid: 2 },
- ];
- const enrichedVulnerability = enrichVulnerabilityWithFeedback(vulnerability, feedbacks);
-
- expect(enrichedVulnerability).toEqual({ ...dismissalVuln, ...issueVuln, ...mrVuln });
- });
- });
-});
diff --git a/spec/frontend/vue_shared/security_reports/utils_spec.js b/spec/frontend/vue_shared/security_reports/utils_spec.js
deleted file mode 100644
index b7129ece698..00000000000
--- a/spec/frontend/vue_shared/security_reports/utils_spec.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import {
- REPORT_TYPE_SAST,
- REPORT_TYPE_SECRET_DETECTION,
- REPORT_FILE_TYPES,
-} from '~/vue_shared/security_reports/constants';
-import {
- extractSecurityReportArtifactsFromMergeRequest,
- extractSecurityReportArtifactsFromPipeline,
-} from '~/vue_shared/security_reports/utils';
-import {
- securityReportMergeRequestDownloadPathsQueryResponse,
- securityReportPipelineDownloadPathsQueryResponse,
- sastArtifacts,
- secretDetectionArtifacts,
- archiveArtifacts,
- traceArtifacts,
- metadataArtifacts,
-} from './mock_data';
-
-describe.each([
- [
- 'extractSecurityReportArtifactsFromMergeRequest',
- extractSecurityReportArtifactsFromMergeRequest,
- securityReportMergeRequestDownloadPathsQueryResponse,
- ],
- [
- 'extractSecurityReportArtifactsFromPipelines',
- extractSecurityReportArtifactsFromPipeline,
- securityReportPipelineDownloadPathsQueryResponse,
- ],
-])('%s', (funcName, extractFunc, response) => {
- it.each`
- reportTypes | expectedArtifacts
- ${[]} | ${[]}
- ${['foo']} | ${[]}
- ${[REPORT_TYPE_SAST]} | ${sastArtifacts}
- ${[REPORT_TYPE_SECRET_DETECTION]} | ${secretDetectionArtifacts}
- ${[REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION]} | ${[...secretDetectionArtifacts, ...sastArtifacts]}
- ${[REPORT_FILE_TYPES.ARCHIVE]} | ${archiveArtifacts}
- ${[REPORT_FILE_TYPES.TRACE]} | ${traceArtifacts}
- ${[REPORT_FILE_TYPES.METADATA]} | ${metadataArtifacts}
- `(
- 'returns the expected artifacts given report types $reportTypes',
- ({ reportTypes, expectedArtifacts }) => {
- expect(extractFunc(reportTypes, response)).toEqual(expectedArtifacts);
- },
- );
-});
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index 2e0711fe18c..885bbc82ecc 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema do
- let_it_be(:connections) { GitlabSchema.connections.all_wrappers }
+ let_it_be(:connections) { described_class.connections.all_wrappers }
let_it_be(:tracers) { described_class.tracers }
let(:user) { build :user }
diff --git a/spec/graphql/graphql_triggers_spec.rb b/spec/graphql/graphql_triggers_spec.rb
index 864818351a1..3f58f2678d8 100644
--- a/spec/graphql/graphql_triggers_spec.rb
+++ b/spec/graphql/graphql_triggers_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
issuable
)
- GraphqlTriggers.issuable_assignees_updated(issuable)
+ described_class.issuable_assignees_updated(issuable)
end
end
@@ -32,7 +32,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
issuable
).and_call_original
- GraphqlTriggers.issuable_title_updated(issuable)
+ described_class.issuable_title_updated(issuable)
end
end
@@ -44,7 +44,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
issuable
).and_call_original
- GraphqlTriggers.issuable_description_updated(issuable)
+ described_class.issuable_description_updated(issuable)
end
end
@@ -62,7 +62,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
issuable
)
- GraphqlTriggers.issuable_labels_updated(issuable)
+ described_class.issuable_labels_updated(issuable)
end
end
@@ -74,7 +74,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
issuable
).and_call_original
- GraphqlTriggers.issuable_dates_updated(issuable)
+ described_class.issuable_dates_updated(issuable)
end
end
@@ -86,7 +86,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
issuable
).and_call_original
- GraphqlTriggers.issuable_milestone_updated(issuable)
+ described_class.issuable_milestone_updated(issuable)
end
end
@@ -100,7 +100,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
merge_request
).and_call_original
- GraphqlTriggers.merge_request_reviewers_updated(merge_request)
+ described_class.merge_request_reviewers_updated(merge_request)
end
end
@@ -114,7 +114,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
merge_request
).and_call_original
- GraphqlTriggers.merge_request_merge_status_updated(merge_request)
+ described_class.merge_request_merge_status_updated(merge_request)
end
end
@@ -128,7 +128,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
merge_request
).and_call_original
- GraphqlTriggers.merge_request_approval_state_updated(merge_request)
+ described_class.merge_request_approval_state_updated(merge_request)
end
end
@@ -140,7 +140,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
issuable
).and_call_original
- GraphqlTriggers.work_item_updated(issuable)
+ described_class.work_item_updated(issuable)
end
context 'when triggered with an Issue' do
@@ -154,7 +154,7 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
work_item
).and_call_original
- GraphqlTriggers.work_item_updated(issue)
+ described_class.work_item_updated(issue)
end
end
end
diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb
index fa0b34113bc..8ce0bc2b70a 100644
--- a/spec/graphql/types/global_id_type_spec.rb
+++ b/spec/graphql/types/global_id_type_spec.rb
@@ -105,12 +105,12 @@ RSpec.describe Types::GlobalIDType do
around do |example|
# Unset all previously memoized GlobalIDTypes to allow us to define one
# that will use the constants stubbed in the `before` block.
- previous_id_types = Types::GlobalIDType.instance_variable_get(:@id_types)
- Types::GlobalIDType.instance_variable_set(:@id_types, {})
+ previous_id_types = described_class.instance_variable_get(:@id_types)
+ described_class.instance_variable_set(:@id_types, {})
example.run
ensure
- Types::GlobalIDType.instance_variable_set(:@id_types, previous_id_types)
+ described_class.instance_variable_set(:@id_types, previous_id_types)
end
before do
diff --git a/spec/initializers/google_api_client_spec.rb b/spec/initializers/google_api_client_spec.rb
index b3c4ac5e23b..cd3e3cf0328 100644
--- a/spec/initializers/google_api_client_spec.rb
+++ b/spec/initializers/google_api_client_spec.rb
@@ -8,7 +8,7 @@ require 'google/apis/core/base_service'
RSpec.describe Google::Apis::Core::HttpCommand do # rubocop:disable RSpec/FilePath
context('with a successful response') do
let(:client) { Google::Apis::Core::BaseService.new('', '').client }
- let(:command) { Google::Apis::Core::HttpCommand.new(:get, 'https://www.googleapis.com/zoo/animals') }
+ let(:command) { described_class.new(:get, 'https://www.googleapis.com/zoo/animals') }
before do
stub_request(:get, 'https://www.googleapis.com/zoo/animals').to_return(body: %(Hello world))
diff --git a/spec/lib/gitlab/checks/file_size_check/any_oversized_blob_spec.rb b/spec/lib/gitlab/checks/file_size_check/any_oversized_blob_spec.rb
new file mode 100644
index 00000000000..bf24d6b63c6
--- /dev/null
+++ b/spec/lib/gitlab/checks/file_size_check/any_oversized_blob_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Checks::FileSizeCheck::AnyOversizedBlob, feature_category: :source_code_management do
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let(:any_blob) do
+ described_class.new(
+ project: project,
+ changes: [{ newrev: 'bf12d2567099e26f59692896f73ac819bae45b00' }],
+ file_size_limit_megabytes: 1)
+ end
+
+ describe '#find!' do
+ subject { any_blob.find! }
+
+ # SHA of the 2-mb-file branch
+ let(:newrev) { 'bf12d2567099e26f59692896f73ac819bae45b00' }
+ let(:timeout) { nil }
+
+ before do
+ # Delete branch so Repository#new_blobs can return results
+ project.repository.delete_branch('2-mb-file')
+ end
+
+ it 'returns the blob exceeding the file size limit' do
+ blob = subject
+ expect(blob).to be_kind_of(Gitlab::Git::Blob)
+ expect(blob.path).to eq('file.bin')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index e5406be0f97..aa7d4b84fb5 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -143,6 +143,7 @@ milestone:
- boards
- milestone_releases
- releases
+- user_agent_detail
snippets:
- author
- project
diff --git a/spec/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder_spec.rb b/spec/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder_spec.rb
index cc85c897019..28f6919e6bc 100644
--- a/spec/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/in_operator_optimization/query_builder_spec.rb
@@ -33,6 +33,18 @@ RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder
]
end
+ let_it_be(:ignored_column_model) do
+ Class.new(ApplicationRecord) do
+ self.table_name = 'issues'
+
+ include IgnorableColumns
+
+ ignore_column :title, remove_with: '16.4', remove_after: '2023-08-22'
+ end
+ end
+
+ let(:scope_model) { Issue }
+ let(:created_records) { issues }
let(:iterator) do
Gitlab::Pagination::Keyset::Iterator.new(
scope: scope.limit(batch_size),
@@ -79,6 +91,55 @@ RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder
end
end
+ context 'when the scope model has ignored columns' do
+ let(:scope) { ignored_column_model.order(id: :desc) }
+ let(:expected_order) { ignored_column_model.where(id: issues.map(&:id)).sort_by(&:id).reverse }
+
+ let(:in_operator_optimization_options) do
+ {
+ array_scope: Project.where(namespace_id: top_level_group.self_and_descendants.select(:id)).select(:id),
+ array_mapping_scope: -> (id_expression) { ignored_column_model.where(ignored_column_model.arel_table[:project_id].eq(id_expression)) },
+ finder_query: -> (id_expression) { ignored_column_model.where(ignored_column_model.arel_table[:id].eq(id_expression)) }
+ }
+ end
+
+ context 'when iterating records one by one' do
+ let(:batch_size) { 1 }
+
+ it_behaves_like 'correct ordering examples'
+
+ context 'when scope selects only some columns' do
+ let(:scope) { ignored_column_model.order(id: :desc).select(:id) }
+
+ it_behaves_like 'correct ordering examples'
+ end
+ end
+
+ context 'when iterating records with LIMIT 3' do
+ let(:batch_size) { 3 }
+
+ it_behaves_like 'correct ordering examples'
+
+ context 'when scope selects only some columns' do
+ let(:scope) { ignored_column_model.order(id: :desc).select(:id) }
+
+ it_behaves_like 'correct ordering examples'
+ end
+ end
+
+ context 'when loading records at once' do
+ let(:batch_size) { issues.size + 1 }
+
+ it_behaves_like 'correct ordering examples'
+
+ context 'when scope selects only some columns' do
+ let(:scope) { ignored_column_model.order(id: :desc).select(:id) }
+
+ it_behaves_like 'correct ordering examples'
+ end
+ end
+ end
+
context 'when ordering by issues.id DESC' do
let(:scope) { Issue.order(id: :desc) }
let(:expected_order) { issues.sort_by(&:id).reverse }
@@ -95,6 +156,14 @@ RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder
let(:batch_size) { 1 }
it_behaves_like 'correct ordering examples'
+
+ context 'when key_set_optimizer_ignored_columns feature flag is disabled' do
+ before do
+ stub_feature_flags(key_set_optimizer_ignored_columns: false)
+ end
+
+ it_behaves_like 'correct ordering examples'
+ end
end
context 'when iterating records with LIMIT 3' do
@@ -332,7 +401,7 @@ RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder
end
context 'when ordering by JOIN-ed columns' do
- let(:scope) { cte_with_issues_and_projects.apply_to(Issue.where({})).reorder(order) }
+ let(:scope) { cte_with_issues_and_projects.apply_to(Issue.where({}).select(Arel.star)).reorder(order) }
let(:cte_with_issues_and_projects) do
cte_query = Issue.select('issues.id AS id', 'project_id', 'projects.id AS projects_id', 'projects.name AS projects_name').joins(:project)
diff --git a/spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb b/spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb
index 5180403b493..c20f3c96734 100644
--- a/spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb
@@ -3,12 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::Strategies::RecordLoaderStrategy do
- let(:finder_query) { -> (created_at_value, id_value) { Project.where(Project.arel_table[:id].eq(id_value)) } }
+ let(:finder_query) { -> (created_at_value, id_value) { model.where(model.arel_table[:id].eq(id_value)) } }
let(:model) { Project }
let(:keyset_scope) do
scope, _ = Gitlab::Pagination::Keyset::SimpleOrderBuilder.build(
- Project.order(:created_at, :id)
+ model.order(:created_at, :id)
)
scope
@@ -22,6 +22,16 @@ RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::Strategies::R
Gitlab::Pagination::Keyset::InOperatorOptimization::OrderByColumns.new(keyset_order.column_definitions, model.arel_table)
end
+ let_it_be(:ignored_column_model) do
+ Class.new(ApplicationRecord) do
+ self.table_name = 'projects'
+
+ include IgnorableColumns
+
+ ignore_column :name, remove_with: '16.4', remove_after: '2023-08-22'
+ end
+ end
+
subject(:strategy) { described_class.new(finder_query, model, order_by_columns) }
describe '#initializer_columns' do
@@ -57,4 +67,32 @@ RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::Strategies::R
expect(strategy.columns).to eq([expected_loader_query.chomp])
end
end
+
+ describe '#final_projections' do
+ context 'when model does not have ignored columns' do
+ it 'does not specify the selected column names' do
+ expect(strategy.final_projections).to contain_exactly("(#{described_class::RECORDS_COLUMN}).*")
+ end
+ end
+
+ context 'when model has ignored columns' do
+ let(:model) { ignored_column_model }
+
+ it 'specifies the selected column names' do
+ expect(strategy.final_projections).to match_array(
+ model.default_select_columns.map { |column| "(#{described_class::RECORDS_COLUMN}).#{column.name}" }
+ )
+ end
+
+ context 'when the key_set_optimizer_ignored_columns feature flag is disabled' do
+ before do
+ stub_feature_flags(key_set_optimizer_ignored_columns: false)
+ end
+
+ it 'does not specify the selected column names' do
+ expect(strategy.final_projections).to contain_exactly("(#{described_class::RECORDS_COLUMN}).*")
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/pagination/keyset/order_spec.rb b/spec/lib/gitlab/pagination/keyset/order_spec.rb
index dda68f6e5ae..a8bdafb1ce8 100644
--- a/spec/lib/gitlab/pagination/keyset/order_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/order_spec.rb
@@ -645,6 +645,16 @@ RSpec.describe Gitlab::Pagination::Keyset::Order do
let_it_be(:user_2) { create(:user, created_at: five_months_ago) }
let_it_be(:user_3) { create(:user, created_at: 1.month.ago) }
let_it_be(:user_4) { create(:user, created_at: 2.months.ago) }
+ let_it_be(:ignored_column_model) do
+ Class.new(ApplicationRecord) do
+ self.table_name = 'users'
+
+ include IgnorableColumns
+ include FromUnion
+
+ ignore_column :username, remove_with: '16.4', remove_after: '2023-08-22'
+ end
+ end
let(:expected_results) { [user_3, user_4, user_2, user_1] }
let(:scope) { User.order(created_at: :desc, id: :desc) }
@@ -672,6 +682,36 @@ RSpec.describe Gitlab::Pagination::Keyset::Order do
iterator_options[:use_union_optimization] = true
end
+ context 'when the scope model has ignored columns' do
+ let(:ignored_expected_results) { expected_results.map { |r| r.becomes(ignored_column_model) } } # rubocop:disable Cop/AvoidBecomes
+
+ context 'when scope selects all columns' do
+ let(:scope) { ignored_column_model.order(created_at: :desc, id: :desc) }
+
+ it 'returns items in the correct order' do
+ expect(items).to eq(ignored_expected_results)
+ end
+ end
+
+ context 'when scope selects only specific columns' do
+ let(:scope) { ignored_column_model.order(created_at: :desc, id: :desc).select(:id, :created_at) }
+
+ it 'returns items in the correct order' do
+ expect(items).to eq(ignored_expected_results)
+ end
+ end
+ end
+
+ context 'when key_set_optimizer_ignored_columns feature flag is disabled' do
+ before do
+ stub_feature_flags(key_set_optimizer_ignored_columns: false)
+ end
+
+ it 'returns items in the correct order' do
+ expect(items).to eq(expected_results)
+ end
+ end
+
it 'returns items in the correct order' do
expect(items).to eq(expected_results)
end
diff --git a/spec/lib/slack/manifest_spec.rb b/spec/lib/slack/manifest_spec.rb
new file mode 100644
index 00000000000..f602f05d260
--- /dev/null
+++ b/spec/lib/slack/manifest_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Slack::Manifest, feature_category: :integrations do
+ describe '.to_h' do
+ it 'creates the correct manifest' do
+ expect(described_class.to_h).to eq({
+ display_information: {
+ name: "GitLab (#{Gitlab.config.gitlab.host})",
+ description: s_('SlackIntegration|Interact with GitLab without leaving your Slack workspace!'),
+ background_color: '#171321',
+ long_description: "Generated for #{Gitlab.config.gitlab.host} by GitLab #{Gitlab::VERSION}.\r\n\r\n" \
+ "- *Notifications:* Get notifications to your team's Slack channel about events " \
+ "happening inside your GitLab projects.\r\n\r\n- *Slash commands:* Quickly open, " \
+ 'access, or close issues from Slack using the `/gitlab` command. Streamline your ' \
+ 'GitLab deployments with ChatOps.'
+ },
+ features: {
+ app_home: {
+ home_tab_enabled: true,
+ messages_tab_enabled: false,
+ messages_tab_read_only_enabled: true
+ },
+ bot_user: {
+ display_name: 'GitLab',
+ always_online: true
+ },
+ slash_commands: [
+ {
+ command: '/gitlab',
+ url: "#{Gitlab.config.gitlab.url}/api/v4/slack/trigger",
+ description: 'GitLab slash commands',
+ usage_hint: 'your-project-name-or-alias command',
+ should_escape: false
+ }
+ ]
+ },
+ oauth_config: {
+ redirect_urls: [
+ Gitlab.config.gitlab.url
+ ],
+ scopes: {
+ bot: %w[
+ commands
+ chat:write
+ chat:write.public
+ ]
+ }
+ },
+ settings: {
+ event_subscriptions: {
+ request_url: "#{Gitlab.config.gitlab.url}/api/v4/integrations/slack/events",
+ bot_events: %w[
+ app_home_opened
+ ]
+ },
+ interactivity: {
+ is_enabled: true,
+ request_url: "#{Gitlab.config.gitlab.url}/api/v4/integrations/slack/interactions",
+ message_menu_options_url: "#{Gitlab.config.gitlab.url}/api/v4/integrations/slack/options"
+ },
+ org_deploy_enabled: false,
+ socket_mode_enabled: false,
+ token_rotation_enabled: false
+ }
+ })
+ end
+ end
+
+ describe '.to_json' do
+ subject(:to_json) { described_class.to_json }
+
+ shared_examples 'a manifest that matches the JSON schema' do
+ it { is_expected.to match_schema('slack/manifest') }
+ end
+
+ it_behaves_like 'a manifest that matches the JSON schema'
+
+ context 'when the host name is very long' do
+ before do
+ allow(Gitlab.config.gitlab).to receive(:host).and_return('abc' * 20)
+ end
+
+ it_behaves_like 'a manifest that matches the JSON schema'
+ end
+ end
+
+ describe '.share_url' do
+ it 'URI encodes the manifest' do
+ allow(described_class).to receive(:to_h).and_return({ foo: 'bar' })
+
+ expect(described_class.share_url).to eq('https://api.slack.com/apps?new_app=1&manifest_json=%7B%22foo%22%3A%22bar%22%7D')
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 76771360e1f..629dfdaf55e 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -109,8 +109,14 @@ RSpec.describe Notify do
is_expected.to have_body_text issue.description
end
- it 'does not add a reason header' do
- is_expected.not_to have_header('X-GitLab-NotificationReason', /.+/)
+ context 'when issue is confidential' do
+ before do
+ issue.update_attribute(:confidential, true)
+ end
+
+ it 'has a confidential header set to true' do
+ is_expected.to have_header('X-GitLab-ConfidentialIssue', 'true')
+ end
end
context 'when sent with a reason' do
@@ -819,6 +825,10 @@ RSpec.describe Notify do
let_it_be(:second_note) { create(:discussion_note_on_issue, in_reply_to: first_note, project: project) }
let_it_be(:third_note) { create(:discussion_note_on_issue, in_reply_to: second_note, project: project) }
+ before_all do
+ first_note.noteable.update_attribute(:confidential, "true")
+ end
+
subject { described_class.note_issue_email(recipient.id, third_note.id) }
it_behaves_like 'an email sent to a user'
@@ -840,17 +850,29 @@ RSpec.describe Notify do
it 'has X-GitLab-Discussion-ID header' do
expect(subject.header['X-GitLab-Discussion-ID'].value).to eq(third_note.discussion.id)
end
+
+ it 'has a confidential header set to true' do
+ is_expected.to have_header('X-GitLab-ConfidentialIssue', 'true')
+ end
end
context 'individual issue comments' do
let_it_be(:note) { create(:note_on_issue, project: project) }
+ before_all do
+ note.noteable.update_attribute(:confidential, "true")
+ end
+
subject { described_class.note_issue_email(recipient.id, note.id) }
it_behaves_like 'an email sent to a user'
it_behaves_like 'appearance header and footer enabled'
it_behaves_like 'appearance header and footer not enabled'
+ it 'has a confidential header set to true' do
+ expect(subject.header['X-GitLab-ConfidentialIssue'].value).to eq('true')
+ end
+
it 'has In-Reply-To header pointing to the issue' do
expect(subject.header['In-Reply-To'].message_ids).to eq(["issue_#{note.noteable.id}@#{host}"])
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 9d7744a0ffd..1f0f89fea60 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Milestone do
+RSpec.describe Milestone, feature_category: :team_planning do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:group) { create(:group) }
@@ -732,4 +732,44 @@ RSpec.describe Milestone do
expect(milestone.lock_version).to be_present
end
end
+
+ describe '#check_for_spam?' do
+ let_it_be(:milestone) { build_stubbed(:milestone, project: project) }
+
+ subject { milestone.check_for_spam? }
+
+ context 'when spammable attribute title has changed' do
+ before do
+ milestone.title = 'New title'
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when spammable attribute description has changed' do
+ before do
+ milestone.description = 'New description'
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when spammable attribute has changed but parent is private' do
+ before do
+ milestone.title = 'New title'
+ milestone.parent.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when no spammable attribute has changed' do
+ before do
+ milestone.title = milestone.title_was
+ milestone.description = milestone.description_was
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
end
diff --git a/spec/rubocop/cop/ignored_columns_spec.rb b/spec/rubocop/cop/ignored_columns_spec.rb
index 8d2c6b92c70..c8f47f8aee9 100644
--- a/spec/rubocop/cop/ignored_columns_spec.rb
+++ b/spec/rubocop/cop/ignored_columns_spec.rb
@@ -4,20 +4,20 @@ require 'rubocop_spec_helper'
require_relative '../../../rubocop/cop/ignored_columns'
RSpec.describe RuboCop::Cop::IgnoredColumns, feature_category: :database do
- it 'flags use of `self.ignored_columns +=` instead of the IgnoredColumns concern' do
+ it 'flags use of `self.ignored_columns +=` instead of the IgnorableColumns concern' do
expect_offense(<<~RUBY)
class Foo < ApplicationRecord
self.ignored_columns += %i[id]
- ^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of adding to `self.ignored_columns`.
+ ^^^^^^^^^^^^^^^ Use `IgnorableColumns` concern instead of adding to `self.ignored_columns`.
end
RUBY
end
- it 'flags use of `self.ignored_columns =` instead of the IgnoredColumns concern' do
+ it 'flags use of `self.ignored_columns =` instead of the IgnorableColumns concern' do
expect_offense(<<~RUBY)
class Foo < ApplicationRecord
self.ignored_columns = %i[id]
- ^^^^^^^^^^^^^^^ Use `IgnoredColumns` concern instead of setting `self.ignored_columns`.
+ ^^^^^^^^^^^^^^^ Use `IgnorableColumns` concern instead of setting `self.ignored_columns`.
end
RUBY
end
diff --git a/spec/serializers/prometheus_alert_entity_spec.rb b/spec/serializers/prometheus_alert_entity_spec.rb
deleted file mode 100644
index 02da5a5bb88..00000000000
--- a/spec/serializers/prometheus_alert_entity_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe PrometheusAlertEntity do
- let(:user) { build_stubbed(:user) }
- let(:prometheus_alert) { build_stubbed(:prometheus_alert) }
- let(:request) { double('prometheus_alert', current_user: user) }
- let(:entity) { described_class.new(prometheus_alert, request: request) }
-
- subject { entity.as_json }
-
- context 'when user can read prometheus alerts' do
- before do
- prometheus_alert.project.add_maintainer(user)
- end
-
- it 'exposes prometheus_alert attributes' do
- expect(subject).to include(:id, :title, :query, :operator, :threshold, :runbook_url)
- end
- end
-end
diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb
index 78cb05532eb..70010d88fbd 100644
--- a/spec/services/milestones/create_service_spec.rb
+++ b/spec/services/milestones/create_service_spec.rb
@@ -3,24 +3,70 @@
require 'spec_helper'
RSpec.describe Milestones::CreateService, feature_category: :team_planning do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:params) { { title: 'New Milestone', description: 'Description' } }
+
+ subject(:create_milestone) { described_class.new(project, user, params) }
describe '#execute' do
- context "valid params" do
+ context 'when milestone is saved successfully' do
+ it 'creates a new milestone' do
+ expect { create_milestone.execute }.to change { Milestone.count }.by(1)
+ end
+
+ it 'opens the milestone if it is a project milestone' do
+ expect_next_instance_of(EventCreateService) do |instance|
+ expect(instance).to receive(:open_milestone)
+ end
+
+ create_milestone.execute
+ end
+
+ it 'returns the created milestone' do
+ milestone = create_milestone.execute
+ expect(milestone).to be_a(Milestone)
+ expect(milestone.title).to eq('New Milestone')
+ expect(milestone.description).to eq('Description')
+ end
+ end
+
+ context 'when milestone fails to save' do
before do
- project.add_maintainer(user)
+ allow_next_instance_of(Milestone) do |instance|
+ allow(instance).to receive(:save).and_return(false)
+ end
+ end
+
+ it 'does not create a new milestone' do
+ expect { create_milestone.execute }.not_to change { Milestone.count }
+ end
- opts = {
- title: 'v2.1.9',
- description: 'Patch release to fix security issue'
- }
+ it 'does not open the milestone' do
+ expect(EventCreateService).not_to receive(:open_milestone)
+
+ create_milestone.execute
+ end
- @milestone = described_class.new(project, user, opts).execute
+ it 'returns the unsaved milestone' do
+ milestone = create_milestone.execute
+ expect(milestone).to be_a(Milestone)
+ expect(milestone.title).to eq('New Milestone')
+ expect(milestone.persisted?).to be_falsey
end
+ end
+
+ it 'calls before_create method' do
+ expect(create_milestone).to receive(:before_create)
+ create_milestone.execute
+ end
+ end
- it { expect(@milestone).to be_valid }
- it { expect(@milestone.title).to eq('v2.1.9') }
+ describe '#before_create' do
+ it 'checks for spam' do
+ milestone = build(:milestone)
+ expect(milestone).to receive(:check_for_spam).with(user: user, action: :create)
+ subject.send(:before_create, milestone)
end
end
end
diff --git a/spec/services/milestones/update_service_spec.rb b/spec/services/milestones/update_service_spec.rb
index 76110af2514..44de49960d4 100644
--- a/spec/services/milestones/update_service_spec.rb
+++ b/spec/services/milestones/update_service_spec.rb
@@ -2,40 +2,86 @@
require 'spec_helper'
RSpec.describe Milestones::UpdateService, feature_category: :team_planning do
- let(:project) { create(:project) }
- let(:user) { build(:user) }
- let(:milestone) { create(:milestone, project: project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:params) { { title: 'New Title' } }
+
+ subject(:update_milestone) { described_class.new(project, user, params) }
describe '#execute' do
- context "valid params" do
- let(:inner_service) { double(:service) }
+ context 'when state_event is "activate"' do
+ let(:params) { { state_event: 'activate' } }
- before do
- project.add_maintainer(user)
+ it 'calls Milestones::ReopenService' do
+ reopen_service = instance_double(Milestones::ReopenService)
+ expect(Milestones::ReopenService).to receive(:new).with(project, user, {}).and_return(reopen_service)
+ expect(reopen_service).to receive(:execute).with(milestone)
+
+ update_milestone.execute(milestone)
end
+ end
- subject { described_class.new(project, user, { title: 'new_title' }).execute(milestone) }
+ context 'when state_event is "close"' do
+ let(:params) { { state_event: 'close' } }
+
+ it 'calls Milestones::CloseService' do
+ close_service = instance_double(Milestones::CloseService)
+ expect(Milestones::CloseService).to receive(:new).with(project, user, {}).and_return(close_service)
+ expect(close_service).to receive(:execute).with(milestone)
+
+ update_milestone.execute(milestone)
+ end
+ end
- it { expect(subject).to be_valid }
- it { expect(subject.title).to eq('new_title') }
+ context 'when params are present' do
+ it 'assigns the params to the milestone' do
+ expect(milestone).to receive(:assign_attributes).with(params.except(:state_event))
- context 'state_event is activate' do
- it 'calls ReopenService' do
- expect(Milestones::ReopenService).to receive(:new).with(project, user, {}).and_return(inner_service)
- expect(inner_service).to receive(:execute).with(milestone)
+ update_milestone.execute(milestone)
+ end
+ end
- described_class.new(project, user, { state_event: 'activate' }).execute(milestone)
- end
+ context 'when milestone is changed' do
+ before do
+ allow(milestone).to receive(:changed?).and_return(true)
end
- context 'state_event is close' do
- it 'calls ReopenService' do
- expect(Milestones::CloseService).to receive(:new).with(project, user, {}).and_return(inner_service)
- expect(inner_service).to receive(:execute).with(milestone)
+ it 'calls before_update' do
+ expect(update_milestone).to receive(:before_update).with(milestone)
- described_class.new(project, user, { state_event: 'close' }).execute(milestone)
- end
+ update_milestone.execute(milestone)
end
end
+
+ context 'when milestone is not changed' do
+ before do
+ allow(milestone).to receive(:changed?).and_return(false)
+ end
+
+ it 'does not call before_update' do
+ expect(update_milestone).not_to receive(:before_update)
+
+ update_milestone.execute(milestone)
+ end
+ end
+
+ it 'saves the milestone' do
+ expect(milestone).to receive(:save)
+
+ update_milestone.execute(milestone)
+ end
+
+ it 'returns the milestone' do
+ expect(update_milestone.execute(milestone)).to eq(milestone)
+ end
+ end
+
+ describe '#before_update' do
+ it 'checks for spam' do
+ expect(milestone).to receive(:check_for_spam).with(user: user, action: :update)
+
+ update_milestone.send(:before_update, milestone)
+ end
end
end
diff --git a/spec/views/admin/application_settings/_slack.html.haml_spec.rb b/spec/views/admin/application_settings/_slack.html.haml_spec.rb
new file mode 100644
index 00000000000..6f89d2b7de4
--- /dev/null
+++ b/spec/views/admin/application_settings/_slack.html.haml_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'admin/application_settings/_slack.html.haml', feature_category: :integrations do
+ let(:app_settings) { build(:application_setting) }
+
+ before do
+ assign(:application_setting, app_settings)
+ end
+
+ it 'renders the form correctly', :aggregate_failures do
+ render
+
+ expect(rendered).to have_field('Client ID', type: 'text')
+ expect(rendered).to have_field('Client secret', type: 'text')
+ expect(rendered).to have_field('Signing secret', type: 'text')
+ expect(rendered).to have_field('Verification token', type: 'text')
+ expect(rendered).to have_link(
+ 'Create Slack app',
+ href: slack_app_manifest_share_admin_application_settings_path
+ )
+ expect(rendered).to have_link(
+ 'Download latest manifest file',
+ href: slack_app_manifest_download_admin_application_settings_path
+ )
+ end
+
+ context 'when GitLab.com', :saas do
+ it 'renders the form correctly', :aggregate_failures do
+ render
+
+ expect(rendered).to have_field('Client ID', type: 'text')
+ expect(rendered).to have_field('Client secret', type: 'text')
+ expect(rendered).to have_field('Signing secret', type: 'text')
+ expect(rendered).to have_field('Verification token', type: 'text')
+
+ expect(rendered).not_to have_link('Create Slack app')
+ expect(rendered).not_to have_link('Download latest manifest file')
+ end
+ end
+end