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>2021-02-12 15:09:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-12 15:09:02 +0300
commit49d26b2348f2eb9e345eb1f66214678f42f15dd3 (patch)
treee68c6c2e50aae17d37a4d5508613b3d93627a5e2 /spec
parent7f5e08060f261a63ebf5058a95419da66928173a (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/boards/boards_spec.rb4
-rw-r--r--spec/features/issues/user_creates_issue_by_email_spec.rb4
-rw-r--r--spec/features/issues/user_resets_their_incoming_email_token_spec.rb24
-rw-r--r--spec/frontend/__mocks__/@gitlab/ui.js4
-rw-r--r--spec/frontend/feature_highlight/feature_highlight_popover_spec.js80
-rw-r--r--spec/frontend/issuable/components/issuable_by_email_spec.js164
-rw-r--r--spec/frontend/issuable_spec.js42
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_details_spec.js10
-rw-r--r--spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js33
-rw-r--r--spec/helpers/events_helper_spec.rb8
-rw-r--r--spec/helpers/projects/alert_management_helper_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb1
-rw-r--r--spec/migrations/add_new_post_eoa_plans_spec.rb32
-rw-r--r--spec/migrations/cleanup_projects_with_bad_has_external_wiki_data_spec.rb89
-rw-r--r--spec/models/event_spec.rb52
-rw-r--r--spec/models/note_spec.rb10
17 files changed, 498 insertions, 63 deletions
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 6c89b08d9c0..2d6b669f28b 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -18,9 +18,9 @@ RSpec.describe 'Issue Boards', :js do
project.add_maintainer(user)
project.add_maintainer(user2)
- set_cookie('sidebar_collapsed', 'true')
-
sign_in(user)
+
+ set_cookie('sidebar_collapsed', 'true')
end
context 'no lists' do
diff --git a/spec/features/issues/user_creates_issue_by_email_spec.rb b/spec/features/issues/user_creates_issue_by_email_spec.rb
index 5a0036170ab..c47f24ab836 100644
--- a/spec/features/issues/user_creates_issue_by_email_spec.rb
+++ b/spec/features/issues/user_creates_issue_by_email_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe 'Issues > User creates issue by email' do
project.add_developer(user)
end
- describe 'new issue by email' do
+ describe 'new issue by email', :js do
shared_examples 'show the email in the modal' do
let(:issue) { create(:issue, project: project) }
@@ -28,7 +28,7 @@ RSpec.describe 'Issues > User creates issue by email' do
page.within '#issuable-email-modal' do
email = project.new_issuable_address(user, 'issue')
- expect(page).to have_selector("input[value='#{email}']")
+ expect(page.find('input[type="text"]').value).to eq email
end
end
end
diff --git a/spec/features/issues/user_resets_their_incoming_email_token_spec.rb b/spec/features/issues/user_resets_their_incoming_email_token_spec.rb
index a20f65abebf..2b1c25174c2 100644
--- a/spec/features/issues/user_resets_their_incoming_email_token_spec.rb
+++ b/spec/features/issues/user_resets_their_incoming_email_token_spec.rb
@@ -16,17 +16,17 @@ RSpec.describe 'Issues > User resets their incoming email token' do
end
it 'changes incoming email address token', :js do
- find('.issuable-email-modal-btn').click
- previous_token = find('input#issuable_email').value
- find('.incoming-email-token-reset').click
-
- wait_for_requests
-
- expect(page).to have_no_field('issuable_email', with: previous_token)
- new_token = project.new_issuable_address(user.reload, 'issue')
- expect(page).to have_field(
- 'issuable_email',
- with: new_token
- )
+ page.find('[data-testid="issuable-email-modal-btn"]').click
+
+ page.within '#issuable-email-modal' do
+ previous_token = page.find('input[type="text"]').value
+ page.find('[data-testid="incoming-email-token-reset"]').click
+
+ wait_for_requests
+
+ expect(page.find('input[type="text"]').value).not_to eq previous_token
+ new_token = project.new_issuable_address(user.reload, 'issue')
+ expect(page.find('input[type="text"]').value).to eq new_token
+ end
end
end
diff --git a/spec/frontend/__mocks__/@gitlab/ui.js b/spec/frontend/__mocks__/@gitlab/ui.js
index 7cdecefab05..ecd67247362 100644
--- a/spec/frontend/__mocks__/@gitlab/ui.js
+++ b/spec/frontend/__mocks__/@gitlab/ui.js
@@ -38,7 +38,9 @@ jest.mock('@gitlab/ui/dist/components/base/popover/popover.js', () => ({
required: false,
default: () => [],
},
- ...Object.fromEntries(['target', 'triggers', 'placement'].map((prop) => [prop, {}])),
+ ...Object.fromEntries(
+ ['target', 'triggers', 'placement', 'boundary', 'container'].map((prop) => [prop, {}]),
+ ),
},
render(h) {
return h(
diff --git a/spec/frontend/feature_highlight/feature_highlight_popover_spec.js b/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
new file mode 100644
index 00000000000..0730cfd453e
--- /dev/null
+++ b/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
@@ -0,0 +1,80 @@
+import { mount } from '@vue/test-utils';
+import { GlPopover, GlLink, GlButton } from '@gitlab/ui';
+import FeatureHighlightPopover from '~/feature_highlight/feature_highlight_popover.vue';
+import { dismiss } from '~/feature_highlight/feature_highlight_helper';
+import { POPOVER_TARGET_ID } from '~/feature_highlight/constants';
+
+jest.mock('~/feature_highlight/feature_highlight_helper');
+
+describe('feature_highlight/feature_highlight_popover', () => {
+ let wrapper;
+ const props = {
+ autoDevopsHelpPath: '/help/autodevops',
+ highlightId: '123',
+ dismissEndpoint: '/api/dismiss',
+ };
+
+ const buildWrapper = (propsData = props) => {
+ wrapper = mount(FeatureHighlightPopover, {
+ propsData,
+ });
+ };
+ const findPopoverTarget = () => wrapper.find(`#${POPOVER_TARGET_ID}`);
+ const findPopover = () => wrapper.findComponent(GlPopover);
+ const findAutoDevopsHelpLink = () => wrapper.findComponent(GlLink);
+ const findDismissButton = () => wrapper.findComponent(GlButton);
+
+ beforeEach(() => {
+ buildWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders popover target', () => {
+ expect(findPopoverTarget().exists()).toBe(true);
+ });
+
+ it('renders popover', () => {
+ expect(findPopover().props()).toMatchObject({
+ target: POPOVER_TARGET_ID,
+ cssClasses: ['feature-highlight-popover'],
+ triggers: 'hover',
+ container: 'body',
+ placement: 'right',
+ boundary: 'viewport',
+ });
+ });
+
+ it('renders link that points to the autodevops help page', () => {
+ expect(findAutoDevopsHelpLink().attributes().href).toBe(props.autoDevopsHelpPath);
+ expect(findAutoDevopsHelpLink().text()).toBe('Auto DevOps');
+ });
+
+ it('renders dismiss button', () => {
+ expect(findDismissButton().props()).toMatchObject({
+ size: 'small',
+ icon: 'thumb-up',
+ variant: 'confirm',
+ });
+ });
+
+ it('dismisses popover when dismiss button is clicked', async () => {
+ await findDismissButton().trigger('click');
+
+ expect(findPopover().emitted('close')).toHaveLength(1);
+ expect(dismiss).toHaveBeenCalledWith(props.dismissEndpoint, props.highlightId);
+ });
+
+ describe('when popover is dismissed and hidden', () => {
+ it('hides the popover target', async () => {
+ await findDismissButton().trigger('click');
+ findPopover().vm.$emit('hidden');
+ await wrapper.vm.$nextTick();
+
+ expect(findPopoverTarget().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/issuable/components/issuable_by_email_spec.js b/spec/frontend/issuable/components/issuable_by_email_spec.js
new file mode 100644
index 00000000000..7e40b903754
--- /dev/null
+++ b/spec/frontend/issuable/components/issuable_by_email_spec.js
@@ -0,0 +1,164 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { shallowMount } from '@vue/test-utils';
+import { GlModal, GlSprintf, GlFormInputGroup, GlButton } from '@gitlab/ui';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import httpStatus from '~/lib/utils/http_status';
+import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
+
+const initialEmail = 'user@gitlab.com';
+
+const mockToastShow = jest.fn();
+
+describe('IssuableByEmail', () => {
+ let wrapper;
+ let mockAxios;
+ let glModalDirective;
+
+ function createComponent(injectedProperties = {}) {
+ glModalDirective = jest.fn();
+
+ return extendedWrapper(
+ shallowMount(IssuableByEmail, {
+ stubs: {
+ GlModal,
+ GlSprintf,
+ GlFormInputGroup,
+ GlButton,
+ },
+ directives: {
+ glModal: {
+ bind(_, { value }) {
+ glModalDirective(value);
+ },
+ },
+ },
+ mocks: {
+ $toast: {
+ show: mockToastShow,
+ },
+ },
+ provide: {
+ issuableType: 'issue',
+ initialEmail,
+ ...injectedProperties,
+ },
+ }),
+ );
+ }
+
+ beforeEach(() => {
+ mockAxios = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ mockAxios.restore();
+ });
+
+ const findFormInputGroup = () => wrapper.find(GlFormInputGroup);
+
+ const clickResetEmail = async () => {
+ wrapper.findByTestId('incoming-email-token-reset').vm.$emit('click');
+
+ await waitForPromises();
+ };
+
+ describe('modal button', () => {
+ it.each`
+ issuableType | buttonText
+ ${'issue'} | ${'Email a new issue to this project'}
+ ${'merge_request'} | ${'Email a new merge request to this project'}
+ `(
+ 'renders a link with "$buttonText" when type is "$issuableType"',
+ ({ issuableType, buttonText }) => {
+ wrapper = createComponent({ issuableType });
+ expect(wrapper.findByTestId('issuable-email-modal-btn').text()).toBe(buttonText);
+ },
+ );
+
+ it('opens the modal when the user clicks the button', () => {
+ wrapper = createComponent();
+
+ wrapper.findByTestId('issuable-email-modal-btn').vm.$emit('click');
+
+ expect(glModalDirective).toHaveBeenCalled();
+ });
+ });
+
+ describe('modal', () => {
+ it('renders a read-only email input field', () => {
+ wrapper = createComponent();
+
+ expect(findFormInputGroup().props('value')).toBe('user@gitlab.com');
+ });
+
+ it.each`
+ issuableType | subject | body
+ ${'issue'} | ${'Enter the issue title'} | ${'Enter the issue description'}
+ ${'merge_request'} | ${'Enter the merge request title'} | ${'Enter the merge request description'}
+ `('renders a mailto button when type is "$issuableType"', ({ issuableType, subject, body }) => {
+ wrapper = createComponent({
+ issuableType,
+ initialEmail,
+ });
+
+ expect(wrapper.findByTestId('mail-to-btn').attributes('href')).toBe(
+ `mailto:${initialEmail}?subject=${subject}&body=${body}`,
+ );
+ });
+
+ describe('reset email', () => {
+ const resetPath = 'gitlab-test/new_issuable_address?issuable_type=issue';
+
+ beforeEach(() => {
+ jest.spyOn(axios, 'put');
+ });
+ it('should send request to reset email token', async () => {
+ wrapper = createComponent({
+ issuableType: 'issue',
+ initialEmail,
+ resetPath,
+ });
+
+ await clickResetEmail();
+
+ expect(axios.put).toHaveBeenCalledWith(resetPath);
+ });
+
+ it('should update the email when the request succeeds', async () => {
+ mockAxios.onPut(resetPath).reply(httpStatus.OK, { new_address: 'foo@bar.com' });
+
+ wrapper = createComponent({
+ issuableType: 'issue',
+ initialEmail,
+ resetPath,
+ });
+
+ await clickResetEmail();
+
+ expect(findFormInputGroup().props('value')).toBe('foo@bar.com');
+ });
+
+ it('should show a toast message when the request fails', async () => {
+ mockAxios.onPut(resetPath).reply(httpStatus.NOT_FOUND, {});
+
+ wrapper = createComponent({
+ issuableType: 'issue',
+ initialEmail,
+ resetPath,
+ });
+
+ await clickResetEmail();
+
+ expect(mockToastShow).toHaveBeenCalledWith(
+ 'There was an error when reseting email token.',
+ { type: 'error' },
+ );
+ expect(findFormInputGroup().props('value')).toBe('user@gitlab.com');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/issuable_spec.js b/spec/frontend/issuable_spec.js
index 6712b8bfd34..9c8f1e04609 100644
--- a/spec/frontend/issuable_spec.js
+++ b/spec/frontend/issuable_spec.js
@@ -1,6 +1,3 @@
-import $ from 'jquery';
-import MockAdaptor from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index';
import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
@@ -22,43 +19,4 @@ describe('Issuable', () => {
expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeDefined();
});
});
-
- describe('resetIncomingEmailToken', () => {
- let mock;
-
- beforeEach(() => {
- const element = document.createElement('a');
- element.classList.add('incoming-email-token-reset');
- element.setAttribute('href', 'foo');
- document.body.appendChild(element);
-
- const input = document.createElement('input');
- input.setAttribute('id', 'issuable_email');
- document.body.appendChild(input);
-
- new IssuableIndex('issue_'); // eslint-disable-line no-new
-
- mock = new MockAdaptor(axios);
-
- mock.onPut('foo').reply(200, {
- new_address: 'testing123',
- });
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('should send request to reset email token', (done) => {
- jest.spyOn(axios, 'put');
- document.querySelector('.incoming-email-token-reset').click();
-
- setImmediate(() => {
- expect(axios.put).toHaveBeenCalledWith('foo');
- expect($('#issuable_email').val()).toBe('testing123');
-
- done();
- });
- });
- });
});
diff --git a/spec/frontend/vue_shared/alert_details/alert_details_spec.js b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
index 40af8e60008..d2ab5878172 100644
--- a/spec/frontend/vue_shared/alert_details/alert_details_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
@@ -90,6 +90,7 @@ describe('AlertDetails', () => {
const findEnvironmentName = () => wrapper.findByTestId('environmentName');
const findEnvironmentPath = () => wrapper.findByTestId('environmentPath');
const findDetailsTable = () => wrapper.find(AlertDetailsTable);
+ const findMetricsTab = () => wrapper.findByTestId('metrics');
describe('Alert details', () => {
describe('when alert is null', () => {
@@ -175,6 +176,15 @@ describe('AlertDetails', () => {
});
});
+ describe('Threat Monitoring details', () => {
+ it('should not render the metrics tab', () => {
+ mountComponent({
+ data: { alert: mockAlert, provide: { isThreatMonitoringPage: true } },
+ });
+ expect(findMetricsTab().exists()).toBe(false);
+ });
+ });
+
describe('Create incident from alert', () => {
it('should display "View incident" button that links the incident page when incident exists', () => {
const issueIid = '3';
diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js
index c2df37821d3..70cf2597963 100644
--- a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js
+++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js
@@ -3,6 +3,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import AlertSidebar from '~/vue_shared/alert_details/components/alert_sidebar.vue';
import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue';
+import SidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue';
import mockAlerts from '../mocks/alerts.json';
const mockAlert = mockAlerts[0];
@@ -11,7 +12,12 @@ describe('Alert Details Sidebar', () => {
let wrapper;
let mock;
- function mountComponent({ mountMethod = shallowMount, stubs = {}, alert = {} } = {}) {
+ function mountComponent({
+ mountMethod = shallowMount,
+ stubs = {},
+ alert = {},
+ provide = {},
+ } = {}) {
wrapper = mountMethod(AlertSidebar, {
data() {
return {
@@ -24,6 +30,7 @@ describe('Alert Details Sidebar', () => {
provide: {
projectPath: 'projectPath',
projectId: '1',
+ ...provide,
},
stubs,
mocks: {
@@ -60,5 +67,29 @@ describe('Alert Details Sidebar', () => {
});
expect(wrapper.find(SidebarAssignees).exists()).toBe(true);
});
+
+ it('should render side bar status dropdown', () => {
+ mountComponent({
+ mountMethod: mount,
+ alert: mockAlert,
+ });
+ expect(wrapper.find(SidebarStatus).exists()).toBe(true);
+ });
+ });
+
+ describe('the sidebar renders for threat monitoring', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mountComponent();
+ });
+
+ it('should not render side bar status dropdown', () => {
+ mountComponent({
+ mountMethod: mount,
+ alert: mockAlert,
+ provide: { isThreatMonitoringPage: true },
+ });
+ expect(wrapper.find(SidebarStatus).exists()).toBe(false);
+ });
});
});
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index c629643e248..264bad92d56 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -200,7 +200,13 @@ RSpec.describe EventsHelper do
it 'returns a project snippet note url' do
event.target = create(:note_on_project_snippet, note: 'keep going')
- expect(subject).to eq("#{project_base_url}/-/snippets/#{event.note_target.id}#note_#{event.target.id}")
+ expect(subject).to eq("#{project_snippet_url(event.note_target.project, event.note_target)}#note_#{event.target.id}")
+ end
+
+ it 'returns a personal snippet note url' do
+ event.target = create(:note_on_personal_snippet, note: 'keep going')
+
+ expect(subject).to eq("#{snippet_url(event.note_target)}#note_#{event.target.id}")
end
it 'returns a project issue url' do
diff --git a/spec/helpers/projects/alert_management_helper_spec.rb b/spec/helpers/projects/alert_management_helper_spec.rb
index 5afa693b400..0df194e460a 100644
--- a/spec/helpers/projects/alert_management_helper_spec.rb
+++ b/spec/helpers/projects/alert_management_helper_spec.rb
@@ -113,7 +113,8 @@ RSpec.describe Projects::AlertManagementHelper do
'alert-id' => alert_id,
'project-path' => project_path,
'project-id' => project_id,
- 'project-issues-path' => issues_path
+ 'project-issues-path' => issues_path,
+ 'page' => 'OPERATIONS'
)
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 69b1499d63c..d0282e14d5f 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -564,6 +564,7 @@ project:
- incident_management_oncall_rotations
- debian_distributions
- merge_request_metrics
+- security_orchestration_policy_configuration
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index a5c9cde4c37..5cc911accbb 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -16,7 +16,6 @@ RSpec.describe Gitlab::InstrumentationHelper do
:rugged_duration_s,
:elasticsearch_calls,
:elasticsearch_duration_s,
- :elasticsearch_timed_out_count,
:mem_objects,
:mem_bytes,
:mem_mallocs,
diff --git a/spec/migrations/add_new_post_eoa_plans_spec.rb b/spec/migrations/add_new_post_eoa_plans_spec.rb
new file mode 100644
index 00000000000..5ae227a6617
--- /dev/null
+++ b/spec/migrations/add_new_post_eoa_plans_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require Rails.root.join('db', 'post_migrate', '20210205104425_add_new_post_eoa_plans.rb')
+
+RSpec.describe AddNewPostEoaPlans do
+ let(:plans) { table(:plans) }
+
+ subject(:migration) { described_class.new }
+
+ describe '#up' do
+ it 'creates the two new records' do
+ expect { migration.up }.to change { plans.count }.by(2)
+
+ new_plans = plans.last(2)
+ expect(new_plans.map(&:name)).to contain_exactly('premium', 'ultimate')
+ end
+ end
+
+ describe '#down' do
+ it 'removes these two new records' do
+ plans.create!(name: 'premium', title: 'Premium (Formerly Silver)')
+ plans.create!(name: 'ultimate', title: 'Ultimate (Formerly Gold)')
+
+ expect { migration.down }.to change { plans.count }.by(-2)
+
+ expect(plans.find_by(name: 'premium')).to be_nil
+ expect(plans.find_by(name: 'ultimate')).to be_nil
+ end
+ end
+end
diff --git a/spec/migrations/cleanup_projects_with_bad_has_external_wiki_data_spec.rb b/spec/migrations/cleanup_projects_with_bad_has_external_wiki_data_spec.rb
new file mode 100644
index 00000000000..ee1f718849f
--- /dev/null
+++ b/spec/migrations/cleanup_projects_with_bad_has_external_wiki_data_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe CleanupProjectsWithBadHasExternalWikiData, :migration do
+ let(:namespace) { table(:namespaces).create!(name: 'foo', path: 'bar') }
+ let(:projects) { table(:projects) }
+ let(:services) { table(:services) }
+
+ def create_projects!(num)
+ Array.new(num) do
+ projects.create!(namespace_id: namespace.id)
+ end
+ end
+
+ def create_active_external_wiki_integrations!(*projects)
+ projects.each do |project|
+ services.create!(type: 'ExternalWikiService', project_id: project.id, active: true)
+ end
+ end
+
+ def create_disabled_external_wiki_integrations!(*projects)
+ projects.each do |project|
+ services.create!(type: 'ExternalWikiService', project_id: project.id, active: false)
+ end
+ end
+
+ def create_active_other_integrations!(*projects)
+ projects.each do |project|
+ services.create!(type: 'NotAnExternalWikiService', project_id: project.id, active: true)
+ end
+ end
+
+ it 'sets `projects.has_external_wiki` correctly' do
+ allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
+
+ project_with_external_wiki_1,
+ project_with_external_wiki_2,
+ project_with_disabled_external_wiki_1,
+ project_with_disabled_external_wiki_2,
+ project_without_external_wiki_1,
+ project_without_external_wiki_2 = create_projects!(6)
+
+ create_active_external_wiki_integrations!(
+ project_with_external_wiki_1,
+ project_with_external_wiki_2
+ )
+
+ create_disabled_external_wiki_integrations!(
+ project_with_disabled_external_wiki_1,
+ project_with_disabled_external_wiki_2
+ )
+
+ create_active_other_integrations!(
+ project_without_external_wiki_1,
+ project_without_external_wiki_2
+ )
+
+ # PG triggers on the services table added in a previous migration
+ # will have set the `has_external_wiki` columns to correct data when
+ # the services records were created above.
+ #
+ # We set the `has_external_wiki` columns for projects to incorrect
+ # data manually below to emulate projects in a state before the PG
+ # triggers were added.
+ project_with_external_wiki_2.update!(has_external_wiki: false)
+ project_with_disabled_external_wiki_2.update!(has_external_wiki: true)
+ project_without_external_wiki_2.update!(has_external_wiki: true)
+
+ migrate!
+
+ expected_true = [
+ project_with_external_wiki_1,
+ project_with_external_wiki_2
+ ].each(&:reload).map(&:has_external_wiki)
+
+ expected_not_true = [
+ project_without_external_wiki_1,
+ project_without_external_wiki_2,
+ project_with_disabled_external_wiki_1,
+ project_with_disabled_external_wiki_2
+ ].each(&:reload).map(&:has_external_wiki)
+
+ expect(expected_true).to all(eq(true))
+ expect(expected_not_true).to all(be_falsey)
+ end
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 47492715c11..a726c6e6513 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -907,6 +907,58 @@ RSpec.describe Event do
end
end
+ context 'with snippet note' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:note_on_project_snippet) { create(:note_on_project_snippet, author: user) }
+ let_it_be(:note_on_personal_snippet) { create(:note_on_personal_snippet, author: user) }
+ let_it_be(:other_note) { create(:note_on_issue, author: user)}
+ let_it_be(:personal_snippet_event) { create(:event, :commented, project: nil, target: note_on_personal_snippet, author: user) }
+ let_it_be(:project_snippet_event) { create(:event, :commented, project: note_on_project_snippet.project, target: note_on_project_snippet, author: user) }
+ let_it_be(:other_event) { create(:event, :commented, project: other_note.project, target: other_note, author: user) }
+
+ describe '#snippet_note?' do
+ it 'returns true for a project snippet event' do
+ expect(project_snippet_event.snippet_note?).to be true
+ end
+
+ it 'returns true for a personal snippet event' do
+ expect(personal_snippet_event.snippet_note?).to be true
+ end
+
+ it 'returns false for a other kinds of event' do
+ expect(other_event.snippet_note?).to be false
+ end
+ end
+
+ describe '#personal_snippet_note?' do
+ it 'returns false for a project snippet event' do
+ expect(project_snippet_event.personal_snippet_note?).to be false
+ end
+
+ it 'returns true for a personal snippet event' do
+ expect(personal_snippet_event.personal_snippet_note?).to be true
+ end
+
+ it 'returns false for a other kinds of event' do
+ expect(other_event.personal_snippet_note?).to be false
+ end
+ end
+
+ describe '#project_snippet_note?' do
+ it 'returns true for a project snippet event' do
+ expect(project_snippet_event.project_snippet_note?).to be true
+ end
+
+ it 'returns false for a personal snippet event' do
+ expect(personal_snippet_event.project_snippet_note?).to be false
+ end
+
+ it 'returns false for a other kinds of event' do
+ expect(other_event.project_snippet_note?).to be false
+ end
+ end
+ end
+
describe '#action_name' do
it 'handles all valid design events' do
created, updated, destroyed, archived = %i[created updated destroyed archived].map do |trait|
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 5c14a3db54e..364b80e8601 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -837,6 +837,16 @@ RSpec.describe Note do
end
end
+ describe '#for_project_snippet?' do
+ it 'returns true for a project snippet note' do
+ expect(build(:note_on_project_snippet).for_project_snippet?).to be true
+ end
+
+ it 'returns false for a personal snippet note' do
+ expect(build(:note_on_personal_snippet).for_project_snippet?).to be false
+ end
+ end
+
describe '#for_personal_snippet?' do
it 'returns false for a project snippet note' do
expect(build(:note_on_project_snippet).for_personal_snippet?).to be_falsy