summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-04-14 15:08:59 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-04-14 15:08:59 +0000
commit9b762f50fee09b50b97b5ab208a9a62522447c8c (patch)
tree4dbd16c66f6aeacc1b88c1e3350df09ce4f91183 /spec
parent9769ccf613ec45634ee32efaf1c39763a759a917 (diff)
downloadgitlab-ce-9b762f50fee09b50b97b5ab208a9a62522447c8c.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/profiles/accounts_controller_spec.rb2
-rw-r--r--spec/controllers/search_controller_spec.rb1
-rw-r--r--spec/controllers/uploads_controller_spec.rb18
-rw-r--r--spec/factories/alert_management/metric_images.rb16
-rw-r--r--spec/features/oauth_login_spec.rb2
-rw-r--r--spec/features/projects/wikis_spec.rb32
-rw-r--r--spec/frontend/content_editor/components/wrappers/media_spec.js (renamed from spec/frontend/content_editor/components/wrappers/image_spec.js)21
-rw-r--r--spec/frontend/content_editor/extensions/attachment_spec.js66
-rw-r--r--spec/helpers/search_helper_spec.rb44
-rw-r--r--spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb22
-rw-r--r--spec/models/alert_management/metric_image_spec.rb26
-rw-r--r--spec/models/ci/build_spec.rb25
-rw-r--r--spec/models/user_spec.rb18
-rw-r--r--spec/policies/alert_management/alert_policy_spec.rb52
-rw-r--r--spec/requests/api/alert_management_alerts_spec.rb411
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/routing/uploads_routing_spec.rb11
-rw-r--r--spec/services/alert_management/metric_images/upload_service_spec.rb79
-rw-r--r--spec/services/deployments/update_environment_service_spec.rb31
-rw-r--r--spec/services/projects/create_service_spec.rb16
20 files changed, 833 insertions, 62 deletions
diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb
index 011528016ce..1b4b67eeaff 100644
--- a/spec/controllers/profiles/accounts_controller_spec.rb
+++ b/spec/controllers/profiles/accounts_controller_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Profiles::AccountsController do
end
end
- [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq, :dingtalk].each do |provider|
+ [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq, :dingtalk, :alicloud].each do |provider|
describe "#{provider} provider" do
let(:user) { create(:omniauth_user, provider: provider.to_s) }
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 9482448fc03..4abcd414e51 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -211,6 +211,7 @@ RSpec.describe SearchController do
:global_search_merge_requests_tab | 'merge_requests'
:global_search_wiki_tab | 'wiki_blobs'
:global_search_commits_tab | 'commits'
+ :global_search_users_tab | 'users'
end
with_them do
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 8442c214cd3..ffcd759435c 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -701,6 +701,24 @@ RSpec.describe UploadsController do
end
end
end
+
+ context 'when viewing alert metric images' do
+ let!(:user) { create(:user) }
+ let!(:project) { create(:project) }
+ let(:alert) { create(:alert_management_alert, project: project) }
+ let(:metric_image) { create(:alert_metric_image, alert: alert) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ it "responds with status 200" do
+ get :show, params: { model: "alert_management_metric_image", mounted_as: 'file', id: metric_image.id, filename: metric_image.filename }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
end
def post_authorize(verified: true)
diff --git a/spec/factories/alert_management/metric_images.rb b/spec/factories/alert_management/metric_images.rb
new file mode 100644
index 00000000000..d7d8182af3e
--- /dev/null
+++ b/spec/factories/alert_management/metric_images.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :alert_metric_image, class: 'AlertManagement::MetricImage' do
+ association :alert, factory: :alert_management_alert
+ url { generate(:url) }
+
+ trait :local do
+ file_store { ObjectStorage::Store::LOCAL }
+ end
+
+ after(:build) do |image|
+ image.file = fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg')
+ end
+ end
+end
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
index 93674057fed..ea5bb8c33b2 100644
--- a/spec/features/oauth_login_spec.rb
+++ b/spec/features/oauth_login_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'OAuth Login', :js, :allow_forgery_protection do
end
providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
- :facebook, :cas3, :auth0, :authentiq, :salesforce, :dingtalk]
+ :facebook, :cas3, :auth0, :authentiq, :salesforce, :dingtalk, :alicloud]
around do |example|
with_omniauth_full_host { example.run }
diff --git a/spec/features/projects/wikis_spec.rb b/spec/features/projects/wikis_spec.rb
index e253d03b411..879ffd2932b 100644
--- a/spec/features/projects/wikis_spec.rb
+++ b/spec/features/projects/wikis_spec.rb
@@ -8,26 +8,14 @@ RSpec.describe 'Project wikis', :js do
let(:wiki) { create(:project_wiki, user: user, project: project) }
let(:project) { create(:project, namespace: user.namespace, creator: user) }
- shared_examples 'wiki feature tests' do
- it_behaves_like 'User creates wiki page'
- it_behaves_like 'User deletes wiki page'
- it_behaves_like 'User previews wiki changes'
- it_behaves_like 'User updates wiki page'
- it_behaves_like 'User uses wiki shortcuts'
- it_behaves_like 'User views AsciiDoc page with includes'
- it_behaves_like 'User views a wiki page'
- it_behaves_like 'User views wiki pages'
- it_behaves_like 'User views wiki sidebar'
- it_behaves_like 'User views Git access wiki page'
- end
-
- it_behaves_like 'wiki feature tests'
-
- context 'when feature flag :wiki_async_load is disabled' do
- before do
- stub_feature_flags(wiki_async_load: false)
- end
-
- it_behaves_like 'wiki feature tests'
- end
+ it_behaves_like 'User creates wiki page'
+ it_behaves_like 'User deletes wiki page'
+ it_behaves_like 'User previews wiki changes'
+ it_behaves_like 'User updates wiki page'
+ it_behaves_like 'User uses wiki shortcuts'
+ it_behaves_like 'User views AsciiDoc page with includes'
+ it_behaves_like 'User views a wiki page'
+ it_behaves_like 'User views wiki pages'
+ it_behaves_like 'User views wiki sidebar'
+ it_behaves_like 'User views Git access wiki page'
end
diff --git a/spec/frontend/content_editor/components/wrappers/image_spec.js b/spec/frontend/content_editor/components/wrappers/media_spec.js
index 7b057f9cabc..3e95e2f3914 100644
--- a/spec/frontend/content_editor/components/wrappers/image_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/media_spec.js
@@ -1,21 +1,24 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { NodeViewWrapper } from '@tiptap/vue-2';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import ImageWrapper from '~/content_editor/components/wrappers/image.vue';
+import MediaWrapper from '~/content_editor/components/wrappers/media.vue';
-describe('content/components/wrappers/image', () => {
+describe('content/components/wrappers/media', () => {
let wrapper;
const createWrapper = async (nodeAttrs = {}) => {
- wrapper = shallowMountExtended(ImageWrapper, {
+ wrapper = shallowMountExtended(MediaWrapper, {
propsData: {
node: {
attrs: nodeAttrs,
+ type: {
+ name: 'image',
+ },
},
},
});
};
- const findImage = () => wrapper.findByTestId('image');
+ const findMedia = () => wrapper.findByTestId('media');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
afterEach(() => {
@@ -33,7 +36,7 @@ describe('content/components/wrappers/image', () => {
createWrapper({ src });
- expect(findImage().attributes().src).toBe(src);
+ expect(findMedia().attributes().src).toBe(src);
});
describe('when uploading', () => {
@@ -45,8 +48,8 @@ describe('content/components/wrappers/image', () => {
expect(findLoadingIcon().exists()).toBe(true);
});
- it('adds gl-opacity-5 class selector to image', () => {
- expect(findImage().classes()).toContain('gl-opacity-5');
+ it('adds gl-opacity-5 class selector to the media tag', () => {
+ expect(findMedia().classes()).toContain('gl-opacity-5');
});
});
@@ -59,8 +62,8 @@ describe('content/components/wrappers/image', () => {
expect(findLoadingIcon().exists()).toBe(false);
});
- it('does not add gl-opacity-5 class selector to image', () => {
- expect(findImage().classes()).not.toContain('gl-opacity-5');
+ it('does not add gl-opacity-5 class selector to the media tag', () => {
+ expect(findMedia().classes()).not.toContain('gl-opacity-5');
});
});
});
diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js
index ec67545cf17..9f035810b9e 100644
--- a/spec/frontend/content_editor/extensions/attachment_spec.js
+++ b/spec/frontend/content_editor/extensions/attachment_spec.js
@@ -1,7 +1,10 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
import Attachment from '~/content_editor/extensions/attachment';
import Image from '~/content_editor/extensions/image';
+import Audio from '~/content_editor/extensions/audio';
+import Video from '~/content_editor/extensions/video';
import Link from '~/content_editor/extensions/link';
import Loading from '~/content_editor/extensions/loading';
import { VARIANT_DANGER } from '~/flash';
@@ -14,6 +17,23 @@ const PROJECT_WIKI_ATTACHMENT_IMAGE_HTML = `<p data-sourcepos="1:1-1:27" dir="au
<img alt="test-file" class="lazy" data-src="/group1/project1/-/wikis/test-file.png" data-canonical-src="test-file.png">
</a>
</p>`;
+
+const PROJECT_WIKI_ATTACHMENT_VIDEO_HTML = `<p data-sourcepos="1:1-1:132" dir="auto">
+ <span class="media-container video-container">
+ <video src="/group1/project1/-/wikis/test-file.mp4" controls="true" data-setup="{}" data-title="test-file" width="400" preload="metadata" data-canonical-src="test-file.mp4">
+ </video>
+ <a href="/himkp/test/-/wikis/test-file.mp4" target="_blank" rel="noopener noreferrer" title="Download 'test-file'" data-canonical-src="test-file.mp4">test-file</a>
+ </span>
+</p>`;
+
+const PROJECT_WIKI_ATTACHMENT_AUDIO_HTML = `<p data-sourcepos="3:1-3:74" dir="auto">
+ <span class="media-container audio-container">
+ <audio src="/himkp/test/-/wikis/test-file.mp3" controls="true" data-setup="{}" data-title="test-file" data-canonical-src="test-file.mp3">
+ </audio>
+ <a href="/himkp/test/-/wikis/test-file.mp3" target="_blank" rel="noopener noreferrer" title="Download 'test-file'" data-canonical-src="test-file.mp3">test-file</a>
+ </span>
+</p>`;
+
const PROJECT_WIKI_ATTACHMENT_LINK_HTML = `<p data-sourcepos="1:1-1:26" dir="auto">
<a href="/group1/project1/-/wikis/test-file.zip" data-canonical-src="test-file.zip">test-file</a>
</p>`;
@@ -23,6 +43,8 @@ describe('content_editor/extensions/attachment', () => {
let doc;
let p;
let image;
+ let audio;
+ let video;
let loading;
let link;
let renderMarkdown;
@@ -31,15 +53,18 @@ describe('content_editor/extensions/attachment', () => {
const uploadsPath = '/uploads/';
const imageFile = new File(['foo'], 'test-file.png', { type: 'image/png' });
+ const audioFile = new File(['foo'], 'test-file.mp3', { type: 'audio/mpeg' });
+ const videoFile = new File(['foo'], 'test-file.mp4', { type: 'video/mp4' });
const attachmentFile = new File(['foo'], 'test-file.zip', { type: 'application/zip' });
const expectDocumentAfterTransaction = ({ number, expectedDoc, action }) => {
return new Promise((resolve) => {
let counter = 1;
- const handleTransaction = () => {
+ const handleTransaction = async () => {
if (counter === number) {
expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc.toJSON());
tiptapEditor.off('update', handleTransaction);
+ await waitForPromises();
resolve();
}
@@ -60,18 +85,22 @@ describe('content_editor/extensions/attachment', () => {
Loading,
Link,
Image,
+ Audio,
+ Video,
Attachment.configure({ renderMarkdown, uploadsPath, eventHub }),
],
});
({
- builders: { doc, p, image, loading, link },
+ builders: { doc, p, image, audio, video, loading, link },
} = createDocBuilder({
tiptapEditor,
names: {
loading: { markType: Loading.name },
image: { nodeType: Image.name },
link: { nodeType: Link.name },
+ audio: { nodeType: Audio.name },
+ video: { nodeType: Video.name },
},
}));
@@ -103,17 +132,22 @@ describe('content_editor/extensions/attachment', () => {
tiptapEditor.commands.setContent(initialDoc.toJSON());
});
- describe('when the file has image mime type', () => {
- const base64EncodedFile = 'data:image/png;base64,Zm9v';
+ describe.each`
+ nodeType | mimeType | html | file | mediaType
+ ${'image'} | ${'image/png'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_HTML} | ${imageFile} | ${(attrs) => image(attrs)}
+ ${'audio'} | ${'audio/mpeg'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${audioFile} | ${(attrs) => audio(attrs)}
+ ${'video'} | ${'video/mp4'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${videoFile} | ${(attrs) => video(attrs)}
+ `('when the file has $nodeType mime type', ({ mimeType, html, file, mediaType }) => {
+ const base64EncodedFile = `data:${mimeType};base64,Zm9v`;
beforeEach(() => {
- renderMarkdown.mockResolvedValue(PROJECT_WIKI_ATTACHMENT_IMAGE_HTML);
+ renderMarkdown.mockResolvedValue(html);
});
describe('when uploading succeeds', () => {
const successResponse = {
link: {
- markdown: '![test-file](test-file.png)',
+ markdown: `![test-file](${file.name})`,
},
};
@@ -121,21 +155,21 @@ describe('content_editor/extensions/attachment', () => {
mock.onPost().reply(httpStatus.OK, successResponse);
});
- it('inserts an image with src set to the encoded image file and uploading true', async () => {
- const expectedDoc = doc(p(image({ uploading: true, src: base64EncodedFile })));
+ it('inserts a media content with src set to the encoded content and uploading true', async () => {
+ const expectedDoc = doc(p(mediaType({ uploading: true, src: base64EncodedFile })));
await expectDocumentAfterTransaction({
number: 1,
expectedDoc,
- action: () => tiptapEditor.commands.uploadAttachment({ file: imageFile }),
+ action: () => tiptapEditor.commands.uploadAttachment({ file }),
});
});
- it('updates the inserted image with canonicalSrc when upload is successful', async () => {
+ it('updates the inserted content with canonicalSrc when upload is successful', async () => {
const expectedDoc = doc(
p(
- image({
- canonicalSrc: 'test-file.png',
+ mediaType({
+ canonicalSrc: file.name,
src: base64EncodedFile,
alt: 'test-file',
uploading: false,
@@ -146,7 +180,7 @@ describe('content_editor/extensions/attachment', () => {
await expectDocumentAfterTransaction({
number: 2,
expectedDoc,
- action: () => tiptapEditor.commands.uploadAttachment({ file: imageFile }),
+ action: () => tiptapEditor.commands.uploadAttachment({ file }),
});
});
});
@@ -162,16 +196,16 @@ describe('content_editor/extensions/attachment', () => {
await expectDocumentAfterTransaction({
number: 2,
expectedDoc,
- action: () => tiptapEditor.commands.uploadAttachment({ file: imageFile }),
+ action: () => tiptapEditor.commands.uploadAttachment({ file }),
});
});
it('emits an alert event that includes an error message', (done) => {
- tiptapEditor.commands.uploadAttachment({ file: imageFile });
+ tiptapEditor.commands.uploadAttachment({ file });
eventHub.$on('alert', ({ message, variant }) => {
expect(variant).toBe(VARIANT_DANGER);
- expect(message).toBe('An error occurred while uploading the image. Please try again.');
+ expect(message).toBe('An error occurred while uploading the file. Please try again.');
done();
});
});
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 78cc1dcee01..96d8157dfde 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -467,6 +467,12 @@ RSpec.describe SearchHelper do
describe '#show_user_search_tab?' do
subject { show_user_search_tab? }
+ let(:current_user) { build(:user) }
+
+ before do
+ allow(self).to receive(:current_user).and_return(current_user)
+ end
+
context 'when project search' do
before do
@project = :some_project
@@ -481,11 +487,14 @@ RSpec.describe SearchHelper do
end
end
- context 'when not project search' do
+ context 'when group search' do
+ before do
+ @group = :some_group
+ end
+
context 'when current_user can read_users_list' do
before do
- allow(self).to receive(:current_user).and_return(:the_current_user)
- allow(self).to receive(:can?).with(:the_current_user, :read_users_list).and_return(true)
+ allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(true)
end
it { is_expected.to eq(true) }
@@ -493,8 +502,33 @@ RSpec.describe SearchHelper do
context 'when current_user cannot read_users_list' do
before do
- allow(self).to receive(:current_user).and_return(:the_current_user)
- allow(self).to receive(:can?).with(:the_current_user, :read_users_list).and_return(false)
+ allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ context 'when global search' do
+ context 'when current_user can read_users_list' do
+ before do
+ allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(true)
+ end
+
+ it { is_expected.to eq(true) }
+
+ context 'when global_search_user_tab feature flag is disabled' do
+ before do
+ stub_feature_flags(global_search_users_tab: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ context 'when current_user cannot read_users_list' do
+ before do
+ allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(false)
end
it { is_expected.to eq(false) }
diff --git a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb
index 15edbc22460..89b284feee0 100644
--- a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb
+++ b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb
@@ -63,6 +63,28 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFile do
expect(nb_file.diff).to be_nil
end
end
+
+ context 'timeout' do
+ it 'utilizes timeout for web' do
+ expect(Timeout).to receive(:timeout).with(described_class::RENDERED_TIMEOUT_FOREGROUND).and_call_original
+
+ nb_file.diff
+ end
+
+ it 'falls back to nil on timeout' do
+ allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+ expect(Timeout).to receive(:timeout).and_raise(Timeout::Error)
+
+ expect(nb_file.diff).to be_nil
+ end
+
+ it 'utilizes longer timeout for sidekiq' do
+ allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
+ expect(Timeout).to receive(:timeout).with(described_class::RENDERED_TIMEOUT_BACKGROUND).and_call_original
+
+ nb_file.diff
+ end
+ end
end
describe '#has_renderable?' do
diff --git a/spec/models/alert_management/metric_image_spec.rb b/spec/models/alert_management/metric_image_spec.rb
new file mode 100644
index 00000000000..dedbd6e501e
--- /dev/null
+++ b/spec/models/alert_management/metric_image_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AlertManagement::MetricImage do
+ subject { build(:alert_metric_image) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:alert) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to be_valid }
+ it { is_expected.to validate_presence_of(:file) }
+ it { is_expected.to validate_length_of(:url).is_at_most(255) }
+ it { is_expected.to validate_length_of(:url_text).is_at_most(128) }
+ end
+
+ describe '.available_for?' do
+ subject { described_class.available_for?(issue.project) }
+
+ let_it_be_with_refind(:issue) { create(:issue) }
+
+ it { is_expected.to eq(true) }
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index a60c6d4d5f3..fd87a388442 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1585,6 +1585,31 @@ RSpec.describe Ci::Build do
it { is_expected.to eq('review/x') }
end
+
+ context 'when environment name uses a nested variable' do
+ let(:yaml_variables) do
+ [
+ { key: 'ENVIRONMENT_NAME', value: '${CI_COMMIT_REF_NAME}' }
+ ]
+ end
+
+ let(:build) do
+ create(:ci_build,
+ ref: 'master',
+ yaml_variables: yaml_variables,
+ environment: 'review/$ENVIRONMENT_NAME')
+ end
+
+ it { is_expected.to eq('review/master') }
+
+ context 'when the FF ci_expand_environment_name_and_url is disabled' do
+ before do
+ stub_feature_flags(ci_expand_environment_name_and_url: false)
+ end
+
+ it { is_expected.to eq('review/${CI_COMMIT_REF_NAME}') }
+ end
+ end
end
describe '#expanded_kubernetes_namespace' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 9f45487dbe4..5f2777b12d2 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1560,7 +1560,7 @@ RSpec.describe User do
end
it 'adds the confirmed primary email to emails' do
- expect(user.emails.confirmed.map(&:email)).not_to include(user.email)
+ expect(user.emails.confirmed.map(&:email)).not_to include(user.unconfirmed_email)
user.confirm
@@ -1619,14 +1619,23 @@ RSpec.describe User do
context 'when the email is changed but not confirmed' do
let(:user) { create(:user, email: 'primary@example.com') }
- it 'does not add the new email to emails yet' do
+ before do
user.update!(email: 'new_primary@example.com')
+ end
+ it 'does not add the new email to emails yet' do
expect(user.unconfirmed_email).to eq('new_primary@example.com')
expect(user.email).to eq('primary@example.com')
expect(user).to be_confirmed
expect(user.emails.pluck(:email)).not_to include('new_primary@example.com')
end
+
+ it 'adds the new email to emails upon confirmation' do
+ user.confirm
+ expect(user.email).to eq('new_primary@example.com')
+ expect(user).to be_confirmed
+ expect(user.emails.pluck(:email)).to include('new_primary@example.com')
+ end
end
context 'when the user is created as not confirmed' do
@@ -1636,6 +1645,11 @@ RSpec.describe User do
expect(user).not_to be_confirmed
expect(user.emails.pluck(:email)).not_to include('primary@example.com')
end
+
+ it 'adds the email to emails upon confirmation' do
+ user.confirm
+ expect(user.emails.pluck(:email)).to include('primary@example.com')
+ end
end
context 'when the user is created as confirmed' do
diff --git a/spec/policies/alert_management/alert_policy_spec.rb b/spec/policies/alert_management/alert_policy_spec.rb
index 3e08d8b4ccc..2027c205c7b 100644
--- a/spec/policies/alert_management/alert_policy_spec.rb
+++ b/spec/policies/alert_management/alert_policy_spec.rb
@@ -3,9 +3,10 @@
require 'spec_helper'
RSpec.describe AlertManagement::AlertPolicy, :models do
- let(:alert) { create(:alert_management_alert) }
- let(:project) { alert.project }
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:alert) { create(:alert_management_alert, project: project, issue: incident) }
+ let_it_be(:incident) { nil }
subject(:policy) { described_class.new(user, alert) }
@@ -21,5 +22,50 @@ RSpec.describe AlertManagement::AlertPolicy, :models do
it { is_expected.to be_allowed :read_alert_management_alert }
it { is_expected.to be_allowed :update_alert_management_alert }
end
+
+ shared_examples 'does not allow metric image reads' do
+ it { expect(policy).to be_disallowed(:read_alert_management_metric_image) }
+ end
+
+ shared_examples 'does not allow metric image updates' do
+ specify do
+ expect(policy).to be_disallowed(:upload_alert_management_metric_image)
+ expect(policy).to be_disallowed(:destroy_alert_management_metric_image)
+ end
+ end
+
+ shared_examples 'allows metric image reads' do
+ it { expect(policy).to be_allowed(:read_alert_management_metric_image) }
+ end
+
+ shared_examples 'allows metric image updates' do
+ specify do
+ expect(policy).to be_allowed(:upload_alert_management_metric_image)
+ expect(policy).to be_allowed(:destroy_alert_management_metric_image)
+ end
+ end
+
+ context 'when user is not a member' do
+ include_examples 'does not allow metric image reads'
+ include_examples 'does not allow metric image updates'
+ end
+
+ context 'when user is a guest' do
+ before do
+ project.add_guest(user)
+ end
+
+ include_examples 'does not allow metric image reads'
+ include_examples 'does not allow metric image updates'
+ end
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ include_examples 'allows metric image reads'
+ include_examples 'allows metric image updates'
+ end
end
end
diff --git a/spec/requests/api/alert_management_alerts_spec.rb b/spec/requests/api/alert_management_alerts_spec.rb
new file mode 100644
index 00000000000..99293e5ae95
--- /dev/null
+++ b/spec/requests/api/alert_management_alerts_spec.rb
@@ -0,0 +1,411 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::AlertManagementAlerts do
+ let_it_be(:creator) { create(:user) }
+ let_it_be(:project) do
+ create(:project, :public, creator_id: creator.id, namespace: creator.namespace)
+ end
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:alert) { create(:alert_management_alert, project: project) }
+
+ describe 'PUT /projects/:id/alert_management_alerts/:alert_iid/metric_images/authorize' do
+ include_context 'workhorse headers'
+
+ before do
+ project.add_developer(user)
+ end
+
+ subject do
+ post api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/authorize", user),
+ headers: workhorse_headers
+ end
+
+ it 'authorizes uploading with workhorse header' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ it 'rejects requests that bypassed gitlab-workhorse' do
+ workhorse_headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'when using remote storage' do
+ context 'when direct upload is enabled' do
+ before do
+ stub_uploads_object_storage(MetricImageUploader, enabled: true, direct_upload: true)
+ end
+
+ it 'responds with status 200, location of file remote store and object details' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response).not_to have_key('TempPath')
+ expect(json_response['RemoteObject']).to have_key('ID')
+ expect(json_response['RemoteObject']).to have_key('GetURL')
+ expect(json_response['RemoteObject']).to have_key('StoreURL')
+ expect(json_response['RemoteObject']).to have_key('DeleteURL')
+ expect(json_response['RemoteObject']).to have_key('MultipartUpload')
+ end
+ end
+
+ context 'when direct upload is disabled' do
+ before do
+ stub_uploads_object_storage(MetricImageUploader, enabled: true, direct_upload: false)
+ end
+
+ it 'handles as a local file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).to eq(MetricImageUploader.workhorse_local_upload_path)
+ expect(json_response['RemoteObject']).to be_nil
+ end
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/alert_management_alerts/:alert_iid/metric_images' do
+ include WorkhorseHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ include_context 'workhorse headers'
+
+ let(:file) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
+ let(:file_name) { 'rails_sample.jpg' }
+ let(:url) { 'http://gitlab.com' }
+ let(:url_text) { 'GitLab' }
+
+ let(:params) { { url: url, url_text: url_text } }
+
+ subject do
+ workhorse_finalize(
+ api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images", user),
+ method: :post,
+ file_key: :file,
+ params: params.merge(file: file),
+ headers: workhorse_headers,
+ send_rewritten_field: true
+ )
+ end
+
+ shared_examples 'can_upload_metric_image' do
+ it 'creates a new metric image' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['filename']).to eq(file_name)
+ expect(json_response['url']).to eq(url)
+ expect(json_response['url_text']).to eq(url_text)
+ expect(json_response['created_at']).not_to be_nil
+ expect(json_response['id']).not_to be_nil
+ file_path_regex = %r{/uploads/-/system/alert_management_metric_image/file/\d+/#{file_name}}
+ expect(json_response['file_path']).to match(file_path_regex)
+ end
+ end
+
+ shared_examples 'unauthorized_upload' do
+ it 'disallows the upload' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+ end
+
+ where(:user_role, :expected_status) do
+ :guest | :unauthorized_upload
+ :reporter | :unauthorized_upload
+ :developer | :can_upload_metric_image
+ end
+
+ with_them do
+ before do
+ # Local storage
+ stub_uploads_object_storage(MetricImageUploader, enabled: false)
+ allow_next_instance_of(MetricImageUploader) do |uploader|
+ allow(uploader).to receive(:file_storage?).and_return(true)
+ end
+
+ project.send("add_#{user_role}", user)
+ end
+
+ it_behaves_like "#{params[:expected_status]}"
+ end
+
+ context 'file size too large' do
+ before do
+ allow_next_instance_of(UploadedFile) do |upload_file|
+ allow(upload_file).to receive(:size).and_return(AlertManagement::MetricImage::MAX_FILE_SIZE + 1)
+ end
+ end
+
+ it 'returns an error' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.body).to match(/File is too large/)
+ end
+ end
+
+ context 'error when saving' do
+ before do
+ project.add_developer(user)
+
+ allow_next_instance_of(::AlertManagement::MetricImages::UploadService) do |service|
+ error = instance_double(ServiceResponse, success?: false, message: 'some error', http_status: :bad_request)
+ allow(service).to receive(:execute).and_return(error)
+ end
+ end
+
+ it 'returns an error' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.body).to match(/some error/)
+ end
+ end
+
+ context 'object storage enabled' do
+ before do
+ # Object storage
+ stub_uploads_object_storage(MetricImageUploader)
+
+ allow_next_instance_of(MetricImageUploader) do |uploader|
+ allow(uploader).to receive(:file_storage?).and_return(true)
+ end
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'can_upload_metric_image'
+
+ it 'uploads to remote storage' do
+ subject
+
+ last_upload = AlertManagement::MetricImage.last.uploads.last
+ expect(last_upload.store).to eq(::ObjectStorage::Store::REMOTE)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/alert_management_alerts/:alert_iid/metric_images' do
+ using RSpec::Parameterized::TableSyntax
+
+ let!(:image) { create(:alert_metric_image, alert: alert) }
+
+ subject { get api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images", user) }
+
+ shared_examples 'can_read_metric_image' do
+ it 'can read the metric images' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first).to match(
+ {
+ id: image.id,
+ created_at: image.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
+ filename: image.filename,
+ file_path: image.file_path,
+ url: image.url,
+ url_text: nil
+ }.with_indifferent_access
+ )
+ end
+ end
+
+ shared_examples 'unauthorized_read' do
+ it 'cannot read the metric images' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ where(:user_role, :public_project, :expected_status) do
+ :not_member | false | :unauthorized_read
+ :not_member | true | :unauthorized_read
+ :guest | false | :unauthorized_read
+ :reporter | false | :unauthorized_read
+ :developer | false | :can_read_metric_image
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :not_member
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) unless public_project
+ end
+
+ it_behaves_like "#{params[:expected_status]}"
+ end
+ end
+
+ describe 'PUT /projects/:id/alert_management_alerts/:alert_iid/metric_images/:metric_image_id' do
+ using RSpec::Parameterized::TableSyntax
+
+ let!(:image) { create(:alert_metric_image, alert: alert) }
+ let(:params) { { url: 'http://test.example.com', url_text: 'Example website 123' } }
+
+ subject do
+ put api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{image.id}", user),
+ params: params
+ end
+
+ shared_examples 'can_update_metric_image' do
+ it 'can update the metric images' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response['url']).to eq(params[:url])
+ expect(json_response['url_text']).to eq(params[:url_text])
+ end
+ end
+
+ shared_examples 'unauthorized_update' do
+ it 'cannot update the metric image' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(image.reload).to eq(image)
+ end
+ end
+
+ where(:user_role, :public_project, :expected_status) do
+ :not_member | false | :unauthorized_update
+ :not_member | true | :unauthorized_update
+ :guest | false | :unauthorized_update
+ :reporter | false | :unauthorized_update
+ :developer | false | :can_update_metric_image
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :not_member
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) unless public_project
+ end
+
+ it_behaves_like "#{params[:expected_status]}"
+ end
+
+ context 'when user has access' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'and metric image not found' do
+ subject do
+ put api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{non_existing_record_id}", user) # rubocop: disable Layout/LineLength
+ end
+
+ it 'returns an error' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('Metric image not found')
+ end
+ end
+
+ context 'metric image cannot be updated' do
+ let(:params) { { url_text: 'something_long' * 100 } }
+
+ it 'returns an error' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq('Metric image could not be updated')
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/alert_management_alerts/:alert_iid/metric_images/:metric_image_id' do
+ using RSpec::Parameterized::TableSyntax
+
+ let!(:image) { create(:alert_metric_image, alert: alert) }
+
+ subject do
+ delete api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{image.id}", user)
+ end
+
+ shared_examples 'can delete metric image successfully' do
+ it 'can delete the metric images' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect { image.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ shared_examples 'unauthorized delete' do
+ it 'cannot delete the metric image' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(image.reload).to eq(image)
+ end
+ end
+
+ where(:user_role, :public_project, :expected_status) do
+ :not_member | false | 'unauthorized delete'
+ :not_member | true | 'unauthorized delete'
+ :guest | false | 'unauthorized delete'
+ :reporter | false | 'unauthorized delete'
+ :developer | false | 'can delete metric image successfully'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :not_member
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) unless public_project
+ end
+
+ it_behaves_like "#{params[:expected_status]}"
+ end
+
+ context 'when user has access' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when metric image not found' do
+ subject do
+ delete api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{non_existing_record_id}", user) # rubocop: disable Layout/LineLength
+ end
+
+ it 'returns an error' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('Metric image not found')
+ end
+ end
+
+ context 'when error when deleting' do
+ before do
+ allow_next_instance_of(AlertManagement::AlertsFinder) do |finder|
+ allow(finder).to receive(:execute).and_return([alert])
+ end
+
+ allow(alert).to receive_message_chain('metric_images.find_by_id') { image }
+ allow(image).to receive(:destroy).and_return(false)
+ end
+
+ it 'returns an error' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq('Metric image could not be deleted')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index d8067321944..7e6d80c047c 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -47,7 +47,7 @@ RSpec.describe API::ProjectImport, :aggregate_failures do
it 'executes a limited number of queries' do
control_count = ActiveRecord::QueryRecorder.new { subject }.count
- expect(control_count).to be <= 105
+ expect(control_count).to be <= 108
end
it 'schedules an import using a namespace' do
diff --git a/spec/routing/uploads_routing_spec.rb b/spec/routing/uploads_routing_spec.rb
index d1ddf8a6d6a..41646d1b515 100644
--- a/spec/routing/uploads_routing_spec.rb
+++ b/spec/routing/uploads_routing_spec.rb
@@ -21,6 +21,17 @@ RSpec.describe 'Uploads', 'routing' do
)
end
+ it 'allows fetching alert metric metric images' do
+ expect(get('/uploads/-/system/alert_management_metric_image/file/1/test.jpg')).to route_to(
+ controller: 'uploads',
+ action: 'show',
+ model: 'alert_management_metric_image',
+ id: '1',
+ filename: 'test.jpg',
+ mounted_as: 'file'
+ )
+ end
+
it 'does not allow creating uploads for other models' do
unroutable_models = UploadsController::MODEL_CLASSES.keys.compact - %w(personal_snippet user)
diff --git a/spec/services/alert_management/metric_images/upload_service_spec.rb b/spec/services/alert_management/metric_images/upload_service_spec.rb
new file mode 100644
index 00000000000..527d9db0fd9
--- /dev/null
+++ b/spec/services/alert_management/metric_images/upload_service_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AlertManagement::MetricImages::UploadService do
+ subject(:service) { described_class.new(alert, current_user, params) }
+
+ let_it_be_with_refind(:project) { create(:project) }
+ let_it_be_with_refind(:alert) { create(:alert_management_alert, project: project) }
+ let_it_be_with_refind(:current_user) { create(:user) }
+
+ let(:params) do
+ {
+ file: fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg'),
+ url: 'https://www.gitlab.com'
+ }
+ end
+
+ describe '#execute' do
+ subject { service.execute }
+
+ shared_examples 'uploads the metric' do
+ it 'uploads the metric and returns a success' do
+ expect { subject }.to change(AlertManagement::MetricImage, :count).by(1)
+ expect(subject.success?).to eq(true)
+ expect(subject.payload).to match({ metric: instance_of(AlertManagement::MetricImage), alert: alert })
+ end
+ end
+
+ shared_examples 'no metric saved, an error given' do |message|
+ it 'returns an error and does not upload', :aggregate_failures do
+ expect(subject.success?).to eq(false)
+ expect(subject.message).to match(a_string_matching(message))
+ expect(AlertManagement::MetricImage.count).to eq(0)
+ end
+ end
+
+ context 'user does not have permissions' do
+ it_behaves_like 'no metric saved, an error given', 'You are not authorized to upload metric images'
+ end
+
+ context 'user has permissions' do
+ before_all do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'uploads the metric'
+
+ context 'no url given' do
+ let(:params) do
+ {
+ file: fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg')
+ }
+ end
+
+ it_behaves_like 'uploads the metric'
+ end
+
+ context 'record invalid' do
+ let(:params) do
+ {
+ file: fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain'),
+ url: 'https://www.gitlab.com'
+ }
+ end
+
+ it_behaves_like 'no metric saved, an error given', /File does not have a supported extension. Only png, jpg, jpeg, gif, bmp, tiff, ico, and webp are supported/ # rubocop: disable Layout/LineLength
+ end
+
+ context 'user is guest' do
+ before_all do
+ project.add_guest(current_user)
+ end
+
+ it_behaves_like 'no metric saved, an error given', 'You are not authorized to upload metric images'
+ end
+ end
+ end
+end
diff --git a/spec/services/deployments/update_environment_service_spec.rb b/spec/services/deployments/update_environment_service_spec.rb
index 6996563fdb8..0859aa2c9d1 100644
--- a/spec/services/deployments/update_environment_service_spec.rb
+++ b/spec/services/deployments/update_environment_service_spec.rb
@@ -286,6 +286,37 @@ RSpec.describe Deployments::UpdateEnvironmentService do
end
end
+ context 'when environment url uses a nested variable' do
+ let(:yaml_variables) do
+ [
+ { key: 'MAIN_DOMAIN', value: '${STACK_NAME}.example.com' },
+ { key: 'STACK_NAME', value: 'appname-${ENVIRONMENT_NAME}' },
+ { key: 'ENVIRONMENT_NAME', value: '${CI_COMMIT_REF_SLUG}' }
+ ]
+ end
+
+ let(:job) do
+ create(:ci_build,
+ :with_deployment,
+ pipeline: pipeline,
+ ref: 'master',
+ environment: 'production',
+ project: project,
+ yaml_variables: yaml_variables,
+ options: { environment: { name: 'production', url: 'http://$MAIN_DOMAIN' } })
+ end
+
+ it { is_expected.to eq('http://appname-master.example.com') }
+
+ context 'when the FF ci_expand_environment_name_and_url is disabled' do
+ before do
+ stub_feature_flags(ci_expand_environment_name_and_url: false)
+ end
+
+ it { is_expected.to eq('http://${STACK_NAME}.example.com') }
+ end
+ end
+
context 'when yaml environment does not have url' do
let(:job) { create(:ci_build, :with_deployment, pipeline: pipeline, environment: 'staging', project: project) }
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 7cf065536ea..c5c5af3cb01 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -135,10 +135,22 @@ RSpec.describe Projects::CreateService, '#execute' do
create_project(user, opts)
end
- it 'builds associated project settings' do
+ it 'creates associated project settings' do
project = create_project(user, opts)
- expect(project.project_setting).to be_new_record
+ expect(project.project_setting).to be_persisted
+ end
+
+ context 'create_project_settings feature flag is disabled' do
+ before do
+ stub_feature_flags(create_project_settings: false)
+ end
+
+ it 'builds associated project settings' do
+ project = create_project(user, opts)
+
+ expect(project.project_setting).to be_new_record
+ end
end
it_behaves_like 'storing arguments in the application context' do