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>2022-03-10 21:09:14 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-10 21:09:14 +0300
commitd18b7dc5eea84db5008986c6879a24ad7f6462a6 (patch)
tree98d6e8635ac32f210f15fcfb3dc583a6295e0b9a /spec
parent6ebe886c82111e1ab9e71d4c02a888d2312898bc (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/boards/boards_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb1
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb8
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb4
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb2
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap2
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js26
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js25
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js12
-rw-r--r--spec/frontend/search/topbar/components/app_spec.js33
-rw-r--r--spec/frontend/security_configuration/components/training_provider_list_spec.js7
-rw-r--r--spec/frontend/security_configuration/mock_data.js12
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js172
-rw-r--r--spec/lib/api/entities/ci/job_artifact_file_spec.rb18
-rw-r--r--spec/lib/api/entities/ci/job_request/dependency_spec.rb27
-rw-r--r--spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb142
-rw-r--r--spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb126
-rw-r--r--spec/lib/gitlab/error_tracking_spec.rb131
-rw-r--r--spec/lib/gitlab/fips_spec.rb42
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/services/ci/job_artifacts/destroy_batch_service_spec.rb76
-rw-r--r--spec/support/helpers/stub_configuration.rb16
-rw-r--r--spec/support/sentry.rb13
-rw-r--r--spec/support/shared_contexts/container_repositories_shared_context.rb9
-rw-r--r--spec/views/projects/empty.html.haml_spec.rb15
27 files changed, 698 insertions, 228 deletions
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index fa01304ffe0..5dd627f3b76 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe 'Project issue boards', :js do
project.add_maintainer(user2)
sign_in(user)
+ stub_feature_flags(gl_avatar_for_all_user_avatars: false)
set_cookie('sidebar_collapsed', 'true')
end
diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
index 33c5a936b8d..fca40dc7edc 100644
--- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
@@ -25,6 +25,7 @@ RSpec.describe 'Merge request > User sees avatars on diff notes', :js do
before do
project.add_maintainer(user)
sign_in user
+ stub_feature_flags(gl_avatar_for_all_user_avatars: false)
set_cookie('sidebar_collapsed', 'true')
end
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index c04a4493a9b..a0016f82f0a 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe 'User searches for code' do
it 'finds code and links to blob' do
fill_in('dashboard_search', with: 'rspec')
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
expect(page).to have_selector('.results', text: 'Update capybara, rspec-rails, poltergeist to recent versions')
@@ -52,7 +52,7 @@ RSpec.describe 'User searches for code' do
it 'finds code and links to blame' do
fill_in('dashboard_search', with: 'rspec')
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
expect(page).to have_selector('.results', text: 'Update capybara, rspec-rails, poltergeist to recent versions')
@@ -65,7 +65,7 @@ RSpec.describe 'User searches for code' do
search = 'for naming files'
fill_in('dashboard_search', with: search)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
expect(page).to have_selector('.results', text: expected_result)
@@ -94,7 +94,7 @@ RSpec.describe 'User searches for code' do
expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
end
it 'persists branch name across search' do
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
end
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index b0902096770..c23a54594d4 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'User searches for issues', :js do
def search_for_issue(search)
fill_in('dashboard_search', with: search)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Issues')
end
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index d7f490ba9bc..61c61d793db 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'User searches for merge requests', :js do
def search_for_mr(search)
fill_in('dashboard_search', with: search)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Merge requests')
end
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 7a1ec16385c..61f2e8e0c8f 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'User searches for milestones', :js do
it 'finds a milestone' do
fill_in('dashboard_search', with: milestone1.title)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Milestones')
page.within('.results') do
@@ -40,7 +40,7 @@ RSpec.describe 'User searches for milestones', :js do
end
fill_in('dashboard_search', with: milestone1.title)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Milestones')
page.within('.results') do
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 06545d8640f..9808383adb7 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe 'User searches for wiki pages', :js do
end
fill_in('dashboard_search', with: search_term)
- find('.btn-search').click
+ find('.gl-search-box-by-click-search-button').click
select_search_scope('Wiki')
page.within('.results') do
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap b/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap
index 9938357ed24..841a9bf8290 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/__snapshots__/settings_form_spec.js.snap
@@ -58,7 +58,7 @@ exports[`Settings Form Remove regex matches snapshot 1`] = `
error=""
label="Remove tags matching:"
name="remove-regex"
- placeholder=".*"
+ placeholder=""
value="asdasdssssdfdf"
/>
`;
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js
index 625aa37fc0f..266f953c3e0 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js
@@ -49,6 +49,11 @@ describe('Settings Form', () => {
const findOlderThanDropdown = () => wrapper.find('[data-testid="older-than-dropdown"]');
const findRemoveRegexInput = () => wrapper.find('[data-testid="remove-regex-input"]');
+ const submitForm = async () => {
+ findForm().trigger('submit');
+ return waitForPromises();
+ };
+
const mountComponent = ({
props = defaultProps,
data,
@@ -318,27 +323,24 @@ describe('Settings Form', () => {
mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
});
- findForm().trigger('submit');
- await waitForPromises();
- await nextTick();
+ await submitForm();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE);
});
describe('when submit fails', () => {
describe('user recoverable errors', () => {
- it('when there is an error is shown in a toast', async () => {
+ it('when there is an error is shown in the nameRegex field t', async () => {
mountComponentWithApollo({
mutationResolver: jest
.fn()
.mockResolvedValue(expirationPolicyMutationPayload({ errors: ['foo'] })),
});
- findForm().trigger('submit');
- await waitForPromises();
- await nextTick();
+ await submitForm();
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('foo');
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE);
+ expect(findRemoveRegexInput().props('error')).toBe('foo');
});
});
@@ -348,9 +350,7 @@ describe('Settings Form', () => {
mutationResolver: jest.fn().mockRejectedValue(expirationPolicyMutationPayload()),
});
- findForm().trigger('submit');
- await waitForPromises();
- await nextTick();
+ await submitForm();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE);
});
@@ -367,9 +367,7 @@ describe('Settings Form', () => {
});
mountComponent({ mocks: { $apollo: { mutate } } });
- findForm().trigger('submit');
- await waitForPromises();
- await nextTick();
+ await submitForm();
expect(findKeepRegexInput().props('error')).toEqual('baz');
});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js
index 4d6bd65bd93..76d5f8a6659 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/graphql/cache_updated_spec.js
@@ -4,15 +4,15 @@ import { updateContainerExpirationPolicy } from '~/packages_and_registries/setti
describe('Registry settings cache update', () => {
let client;
- const payload = {
+ const payload = (value) => ({
data: {
updateContainerExpirationPolicy: {
containerExpirationPolicy: {
- enabled: true,
+ ...value,
},
},
},
- };
+ });
const cacheMock = {
project: {
@@ -35,12 +35,12 @@ describe('Registry settings cache update', () => {
});
describe('Registry settings cache update', () => {
it('calls readQuery', () => {
- updateContainerExpirationPolicy('foo')(client, payload);
+ updateContainerExpirationPolicy('foo')(client, payload({ enabled: true }));
expect(client.readQuery).toHaveBeenCalledWith(queryAndVariables);
});
it('writes the correct result in the cache', () => {
- updateContainerExpirationPolicy('foo')(client, payload);
+ updateContainerExpirationPolicy('foo')(client, payload({ enabled: true }));
expect(client.writeQuery).toHaveBeenCalledWith({
...queryAndVariables,
data: {
@@ -52,5 +52,20 @@ describe('Registry settings cache update', () => {
},
});
});
+
+ it('with an empty update preserves the state', () => {
+ updateContainerExpirationPolicy('foo')(client, payload());
+
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ ...queryAndVariables,
+ data: {
+ project: {
+ containerExpirationPolicy: {
+ enabled: false,
+ },
+ },
+ },
+ });
+ });
});
});
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
index ee682b18af3..5f1aff99578 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
@@ -9,7 +9,6 @@ import { testActions, testSections, testProject } from './mock_data';
describe('Learn GitLab', () => {
let wrapper;
let sidebar;
- let inviteMembers = false;
const createWrapper = () => {
wrapper = mount(LearnGitlab, {
@@ -17,7 +16,6 @@ describe('Learn GitLab', () => {
actions: testActions,
sections: testSections,
project: testProject,
- inviteMembers,
},
});
};
@@ -38,7 +36,6 @@ describe('Learn GitLab', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
- inviteMembers = false;
sidebar.remove();
});
@@ -73,7 +70,6 @@ describe('Learn GitLab', () => {
});
it('emits openModal', () => {
- inviteMembers = true;
Cookies.set(INVITE_MODAL_OPEN_COOKIE, true);
createWrapper();
@@ -86,19 +82,11 @@ describe('Learn GitLab', () => {
});
it('does not emit openModal when cookie is not set', () => {
- inviteMembers = true;
-
createWrapper();
expect(spy).not.toHaveBeenCalled();
expect(cookieSpy).toHaveBeenCalledWith(INVITE_MODAL_OPEN_COOKIE);
});
-
- it('does not emit openModal when inviteMembers is false', () => {
- createWrapper();
-
- expect(spy).not.toHaveBeenCalled();
- });
});
describe('when the showSuccessfulInvitationsAlert event is fired', () => {
diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js
index 7ce5efb3c52..0a44688bfe0 100644
--- a/spec/frontend/search/topbar/components/app_spec.js
+++ b/spec/frontend/search/topbar/components/app_spec.js
@@ -1,4 +1,4 @@
-import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui';
+import { GlSearchBoxByClick } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
@@ -36,40 +36,19 @@ describe('GlobalSearchTopbar', () => {
wrapper.destroy();
});
- const findTopbarForm = () => wrapper.find(GlForm);
- const findGlSearchBox = () => wrapper.find(GlSearchBoxByType);
+ const findGlSearchBox = () => wrapper.find(GlSearchBoxByClick);
const findGroupFilter = () => wrapper.find(GroupFilter);
const findProjectFilter = () => wrapper.find(ProjectFilter);
- const findSearchButton = () => wrapper.find(GlButton);
describe('template', () => {
beforeEach(() => {
createComponent();
});
- it('renders Topbar Form always', () => {
- expect(findTopbarForm().exists()).toBe(true);
- });
-
describe('Search box', () => {
it('renders always', () => {
expect(findGlSearchBox().exists()).toBe(true);
});
-
- describe('onSearch', () => {
- const testSearch = 'test search';
-
- beforeEach(() => {
- findGlSearchBox().vm.$emit('input', testSearch);
- });
-
- it('calls setQuery when input event is fired from GlSearchBoxByType', () => {
- expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
- key: 'search',
- value: testSearch,
- });
- });
- });
});
describe.each`
@@ -92,10 +71,6 @@ describe('GlobalSearchTopbar', () => {
expect(findProjectFilter().exists()).toBe(showFilters);
});
});
-
- it('renders SearchButton always', () => {
- expect(findSearchButton().exists()).toBe(true);
- });
});
describe('actions', () => {
@@ -103,8 +78,8 @@ describe('GlobalSearchTopbar', () => {
createComponent();
});
- it('clicking SearchButton calls applyQuery', () => {
- findTopbarForm().vm.$emit('submit', { preventDefault: () => {} });
+ it('clicking search button inside search box calls applyQuery', () => {
+ findGlSearchBox().vm.$emit('submit', { preventDefault: () => {} });
expect(actionSpies.applyQuery).toHaveBeenCalled();
});
diff --git a/spec/frontend/security_configuration/components/training_provider_list_spec.js b/spec/frontend/security_configuration/components/training_provider_list_spec.js
index 08ba4bcbf69..5e2efa2425c 100644
--- a/spec/frontend/security_configuration/components/training_provider_list_spec.js
+++ b/spec/frontend/security_configuration/components/training_provider_list_spec.js
@@ -26,6 +26,7 @@ import {
updateSecurityTrainingProvidersErrorResponse,
testProjectPath,
testProviderIds,
+ testProviderName,
tempProviderLogos,
} from '../mock_data';
@@ -207,9 +208,13 @@ describe('TrainingProviderList component', () => {
expect(findLogos().at(provider).attributes('width')).toBe('18');
});
+ it.each(providerIndexArray)('has a11y decorative attribute for provider %s', (provider) => {
+ expect(findLogos().at(provider).attributes('role')).toBe('presentation');
+ });
+
it.each(providerIndexArray)('displays the correct svg path for provider %s', (provider) => {
expect(findLogos().at(provider).attributes('src')).toBe(
- tempProviderLogos[testProviderIds[provider]].svg,
+ tempProviderLogos[testProviderName[provider]].svg,
);
});
});
diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js
index 588fac11987..3bad687740c 100644
--- a/spec/frontend/security_configuration/mock_data.js
+++ b/spec/frontend/security_configuration/mock_data.js
@@ -1,11 +1,11 @@
export const testProjectPath = 'foo/bar';
-
export const testProviderIds = [101, 102, 103];
+export const testProviderName = ['Vendor Name 1', 'Vendor Name 2', 'Vendor Name 3'];
const createSecurityTrainingProviders = ({ providerOverrides = {} }) => [
{
id: testProviderIds[0],
- name: 'Vendor Name 1',
+ name: testProviderName[0],
description: 'Interactive developer security education',
url: 'https://www.example.org/security/training',
isEnabled: false,
@@ -14,7 +14,7 @@ const createSecurityTrainingProviders = ({ providerOverrides = {} }) => [
},
{
id: testProviderIds[1],
- name: 'Vendor Name 2',
+ name: testProviderName[1],
description: 'Security training with guide and learning pathways.',
url: 'https://www.vendornametwo.com/',
isEnabled: false,
@@ -23,7 +23,7 @@ const createSecurityTrainingProviders = ({ providerOverrides = {} }) => [
},
{
id: testProviderIds[2],
- name: 'Vendor Name 3',
+ name: testProviderName[2],
description: 'Security training for the everyday developer.',
url: 'https://www.vendornamethree.com/',
isEnabled: false,
@@ -99,10 +99,10 @@ export const updateSecurityTrainingProvidersErrorResponse = {
// Will remove once this issue is resolved where the svg path will be available in the GraphQL query
// https://gitlab.com/gitlab-org/gitlab/-/issues/346899
export const tempProviderLogos = {
- [testProviderIds[0]]: {
+ [testProviderName[0]]: {
svg: '/assets/illustrations/vulnerability/vendor-1.svg',
},
- [testProviderIds[1]]: {
+ [testProviderName[1]]: {
svg: '/assets/illustrations/vulnerability/vendor-2.svg',
},
};
diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
index 2c3fc70e116..64ce210b6c8 100644
--- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
@@ -1,12 +1,13 @@
import { shallowMount } from '@vue/test-utils';
+import { GlAvatar, GlTooltip } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '~/lazy_loader';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
jest.mock('images/no_avatar.png', () => 'default-avatar-url');
-const DEFAULT_PROPS = {
- size: 99,
+const PROVIDED_PROPS = {
+ size: 32,
imgSrc: 'myavatarurl.com',
imgAlt: 'mydisplayname',
cssClasses: 'myextraavatarclass',
@@ -14,6 +15,10 @@ const DEFAULT_PROPS = {
tooltipPlacement: 'bottom',
};
+const DEFAULT_PROPS = {
+ size: 20,
+};
+
describe('User Avatar Image Component', () => {
let wrapper;
@@ -21,64 +26,149 @@ describe('User Avatar Image Component', () => {
wrapper.destroy();
});
- describe('Initialization', () => {
- beforeEach(() => {
- wrapper = shallowMount(UserAvatarImage, {
- propsData: {
- ...DEFAULT_PROPS,
- },
+ describe('`glAvatarForAllUserAvatars` feature flag enabled', () => {
+ describe('Initialization', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage, {
+ propsData: {
+ ...PROVIDED_PROPS,
+ },
+ provide: {
+ glFeatures: {
+ glAvatarForAllUserAvatars: true,
+ },
+ },
+ });
+ });
+
+ it('should render `GlAvatar` and provide correct properties to it', () => {
+ const avatar = wrapper.findComponent(GlAvatar);
+
+ expect(avatar.attributes('data-src')).toBe(
+ `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ );
+ expect(avatar.props()).toMatchObject({
+ src: `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ alt: PROVIDED_PROPS.imgAlt,
+ });
+ });
+
+ it('should add correct CSS classes', () => {
+ const classes = wrapper.findComponent(GlAvatar).classes();
+ expect(classes).toContain(PROVIDED_PROPS.cssClasses);
+ expect(classes).not.toContain('lazy');
});
});
- it('should have <img> as a child element', () => {
- const imageElement = wrapper.find('img');
+ describe('Initialization when lazy', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage, {
+ propsData: {
+ ...PROVIDED_PROPS,
+ lazy: true,
+ },
+ provide: {
+ glFeatures: {
+ glAvatarForAllUserAvatars: true,
+ },
+ },
+ });
+ });
+
+ it('should add lazy attributes', () => {
+ const avatar = wrapper.findComponent(GlAvatar);
- expect(imageElement.exists()).toBe(true);
- expect(imageElement.attributes('src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`);
- expect(imageElement.attributes('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`);
- expect(imageElement.attributes('alt')).toBe(DEFAULT_PROPS.imgAlt);
+ expect(avatar.classes()).toContain('lazy');
+ expect(avatar.attributes()).toMatchObject({
+ src: placeholderImage,
+ 'data-src': `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ });
+ });
});
- it('should properly render img css', () => {
- const classes = wrapper.find('img').classes();
- expect(classes).toEqual(expect.arrayContaining(['avatar', 's99', DEFAULT_PROPS.cssClasses]));
- expect(classes).not.toContain('lazy');
+ describe('Initialization without src', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage);
+ });
+
+ it('should have default avatar image', () => {
+ const imageElement = wrapper.find('img');
+
+ expect(imageElement.attributes('src')).toBe(
+ `${defaultAvatarUrl}?width=${DEFAULT_PROPS.size}`,
+ );
+ });
});
});
- describe('Initialization when lazy', () => {
- beforeEach(() => {
- wrapper = shallowMount(UserAvatarImage, {
- propsData: {
- ...DEFAULT_PROPS,
- lazy: true,
- },
+ describe('`glAvatarForAllUserAvatars` feature flag disabled', () => {
+ describe('Initialization', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage, {
+ propsData: {
+ ...PROVIDED_PROPS,
+ },
+ });
});
- });
- it('should add lazy attributes', () => {
- const imageElement = wrapper.find('img');
+ it('should have <img> as a child element', () => {
+ const imageElement = wrapper.find('img');
+
+ expect(imageElement.exists()).toBe(true);
+ expect(imageElement.attributes('src')).toBe(
+ `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ );
+ expect(imageElement.attributes('data-src')).toBe(
+ `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ );
+ expect(imageElement.attributes('alt')).toBe(PROVIDED_PROPS.imgAlt);
+ });
- expect(imageElement.classes()).toContain('lazy');
- expect(imageElement.attributes('src')).toBe(placeholderImage);
- expect(imageElement.attributes('data-src')).toBe(`${DEFAULT_PROPS.imgSrc}?width=99`);
+ it('should properly render img css', () => {
+ const classes = wrapper.find('img').classes();
+ expect(classes).toEqual(['avatar', 's32', PROVIDED_PROPS.cssClasses]);
+ expect(classes).not.toContain('lazy');
+ });
});
- });
- describe('Initialization without src', () => {
- beforeEach(() => {
- wrapper = shallowMount(UserAvatarImage);
+ describe('Initialization when lazy', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage, {
+ propsData: {
+ ...PROVIDED_PROPS,
+ lazy: true,
+ },
+ });
+ });
+
+ it('should add lazy attributes', () => {
+ const imageElement = wrapper.find('img');
+
+ expect(imageElement.classes()).toContain('lazy');
+ expect(imageElement.attributes('src')).toBe(placeholderImage);
+ expect(imageElement.attributes('data-src')).toBe(
+ `${PROVIDED_PROPS.imgSrc}?width=${PROVIDED_PROPS.size}`,
+ );
+ });
});
- it('should have default avatar image', () => {
- const imageElement = wrapper.find('img');
+ describe('Initialization without src', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarImage);
+ });
+
+ it('should have default avatar image', () => {
+ const imageElement = wrapper.find('img');
- expect(imageElement.attributes('src')).toBe(`${defaultAvatarUrl}?width=20`);
+ expect(imageElement.attributes('src')).toBe(
+ `${defaultAvatarUrl}?width=${DEFAULT_PROPS.size}`,
+ );
+ });
});
});
describe('dynamic tooltip content', () => {
- const props = DEFAULT_PROPS;
+ const props = PROVIDED_PROPS;
const slots = {
default: ['Action!'],
};
@@ -91,11 +181,11 @@ describe('User Avatar Image Component', () => {
});
it('renders the tooltip slot', () => {
- expect(wrapper.find('.js-user-avatar-image-tooltip').exists()).toBe(true);
+ expect(wrapper.findComponent(GlTooltip).exists()).toBe(true);
});
it('renders the tooltip content', () => {
- expect(wrapper.find('.js-user-avatar-image-tooltip').text()).toContain(slots.default[0]);
+ expect(wrapper.findComponent(GlTooltip).text()).toContain(slots.default[0]);
});
it('does not render tooltip data attributes for on avatar image', () => {
diff --git a/spec/lib/api/entities/ci/job_artifact_file_spec.rb b/spec/lib/api/entities/ci/job_artifact_file_spec.rb
new file mode 100644
index 00000000000..9e4ec272518
--- /dev/null
+++ b/spec/lib/api/entities/ci/job_artifact_file_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Ci::JobArtifactFile do
+ let(:artifact_file) { instance_double(JobArtifactUploader, filename: 'ci_build_artifacts.zip', cached_size: 42) }
+ let(:entity) { described_class.new(artifact_file) }
+
+ subject { entity.as_json }
+
+ it 'returns the filename' do
+ expect(subject[:filename]).to eq('ci_build_artifacts.zip')
+ end
+
+ it 'returns the size' do
+ expect(subject[:size]).to eq(42)
+ end
+end
diff --git a/spec/lib/api/entities/ci/job_request/dependency_spec.rb b/spec/lib/api/entities/ci/job_request/dependency_spec.rb
new file mode 100644
index 00000000000..fa5f3da554c
--- /dev/null
+++ b/spec/lib/api/entities/ci/job_request/dependency_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Ci::JobRequest::Dependency do
+ let(:job) { create(:ci_build, :artifacts) }
+ let(:entity) { described_class.new(job) }
+
+ subject { entity.as_json }
+
+ it 'returns the dependency id' do
+ expect(subject[:id]).to eq(job.id)
+ end
+
+ it 'returns the dependency name' do
+ expect(subject[:name]).to eq(job.name)
+ end
+
+ it 'returns the dependency token' do
+ expect(subject[:token]).to eq(job.token)
+ end
+
+ it 'returns the dependency artifacts_file', :aggregate_failures do
+ expect(subject[:artifacts_file][:filename]).to eq('ci_build_artifacts.zip')
+ expect(subject[:artifacts_file][:size]).to eq(job.artifacts_size)
+ end
+end
diff --git a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
index 9acc7fd04be..33d322d0d44 100644
--- a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
@@ -2,9 +2,9 @@
require 'spec_helper'
-RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
+RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor, :sentry do
describe '.call' do
- let(:required_options) do
+ let(:raven_required_options) do
{
configuration: Raven.configuration,
context: Raven.context,
@@ -12,7 +12,15 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
}
end
- let(:event) { Raven::Event.from_exception(exception, required_options.merge(data)) }
+ let(:raven_event) do
+ Raven::Event
+ .from_exception(exception, raven_required_options.merge(data))
+ end
+
+ let(:sentry_event) do
+ Sentry.get_current_client.event_from_exception(exception)
+ end
+
let(:result_hash) { described_class.call(event).to_hash }
let(:data) do
@@ -27,36 +35,43 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
}
end
+ before do
+ Sentry.get_current_scope.update_from_options(**data)
+ Sentry.get_current_scope.apply_to_event(sentry_event)
+ end
+
+ after do
+ Sentry.get_current_scope.clear
+ end
+
context 'when there is no GRPC exception' do
let(:exception) { RuntimeError.new }
let(:data) { { fingerprint: ['ArgumentError', 'Missing arguments'] } }
- it 'leaves data unchanged' do
- expect(result_hash).to include(data)
+ shared_examples 'leaves data unchanged' do
+ it { expect(result_hash).to include(data) }
end
- end
- context 'when there is a GRPC exception with a debug string' do
- let(:exception) { GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}') }
+ context 'with Raven event' do
+ let(:event) { raven_event }
- it 'removes the debug error string and stores it as an extra field' do
- expect(result_hash[:fingerprint])
- .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
+ it_behaves_like 'leaves data unchanged'
+ end
- expect(result_hash[:exception][:values].first)
- .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+ context 'with Sentry event' do
+ let(:event) { sentry_event }
- expect(result_hash[:extra])
- .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
+ it_behaves_like 'leaves data unchanged'
end
+ end
- context 'with no custom fingerprint' do
- let(:data) do
- { extra: { caller: 'test' } }
- end
+ context 'when there is a GRPC exception with a debug string' do
+ let(:exception) { GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}') }
+ shared_examples 'processes the exception' do
it 'removes the debug error string and stores it as an extra field' do
- expect(result_hash).not_to include(:fingerprint)
+ expect(result_hash[:fingerprint])
+ .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
expect(result_hash[:exception][:values].first)
.to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
@@ -64,11 +79,42 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
expect(result_hash[:extra])
.to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
end
+
+ context 'with no custom fingerprint' do
+ let(:data) do
+ { extra: { caller: 'test' } }
+ end
+
+ it 'removes the debug error string and stores it as an extra field' do
+ expect(result_hash[:fingerprint]).to be_blank
+
+ expect(result_hash[:exception][:values].first)
+ .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:extra])
+ .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
+ end
+ end
+ end
+
+ context 'with Raven event' do
+ let(:event) { raven_event }
+
+ it_behaves_like 'processes the exception'
+ end
+
+ context 'with Sentry event' do
+ let(:event) { sentry_event }
+
+ it_behaves_like 'processes the exception'
end
end
context 'when there is a wrapped GRPC exception with a debug string' do
- let(:inner_exception) { GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}') }
+ let(:inner_exception) do
+ GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}')
+ end
+
let(:exception) do
begin
raise inner_exception
@@ -79,27 +125,10 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
e
end
- it 'removes the debug error string and stores it as an extra field' do
- expect(result_hash[:fingerprint])
- .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
-
- expect(result_hash[:exception][:values].first)
- .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
-
- expect(result_hash[:exception][:values].second)
- .to include(type: 'StandardError', value: '4:Deadline Exceeded.')
-
- expect(result_hash[:extra])
- .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
- end
-
- context 'with no custom fingerprint' do
- let(:data) do
- { extra: { caller: 'test' } }
- end
-
+ shared_examples 'processes the exception' do
it 'removes the debug error string and stores it as an extra field' do
- expect(result_hash).not_to include(:fingerprint)
+ expect(result_hash[:fingerprint])
+ .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
expect(result_hash[:exception][:values].first)
.to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
@@ -110,6 +139,37 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
expect(result_hash[:extra])
.to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
end
+
+ context 'with no custom fingerprint' do
+ let(:data) do
+ { extra: { caller: 'test' } }
+ end
+
+ it 'removes the debug error string and stores it as an extra field' do
+ expect(result_hash[:fingerprint]).to be_blank
+
+ expect(result_hash[:exception][:values].first)
+ .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:exception][:values].second)
+ .to include(type: 'StandardError', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:extra])
+ .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
+ end
+ end
+ end
+
+ context 'with Raven event' do
+ let(:event) { raven_event }
+
+ it_behaves_like 'processes the exception'
+ end
+
+ context 'with Sentry event' do
+ let(:event) { sentry_event }
+
+ it_behaves_like 'processes the exception'
end
end
end
diff --git a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
index 3febc10831a..d33f8393904 100644
--- a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rspec-parameterized'
-RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
+RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor, :sentry do
after do
if described_class.instance_variable_defined?(:@permitted_arguments_for_worker)
described_class.remove_instance_variable(:@permitted_arguments_for_worker)
@@ -95,7 +95,9 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
end
describe '.call' do
- let(:required_options) do
+ let(:exception) { StandardError.new('Test exception') }
+
+ let(:raven_required_options) do
{
configuration: Raven.configuration,
context: Raven.context,
@@ -103,9 +105,25 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
}
end
- let(:event) { Raven::Event.new(required_options.merge(wrapped_value)) }
+ let(:raven_event) do
+ Raven::Event.new(raven_required_options.merge(wrapped_value))
+ end
+
+ let(:sentry_event) do
+ Sentry.get_current_client.event_from_exception(exception)
+ end
+
let(:result_hash) { described_class.call(event).to_hash }
+ before do
+ Sentry.get_current_scope.update_from_options(**wrapped_value)
+ Sentry.get_current_scope.apply_to_event(sentry_event)
+ end
+
+ after do
+ Sentry.get_current_scope.clear
+ end
+
context 'when there is Sidekiq data' do
let(:wrapped_value) { { extra: { sidekiq: value } } }
@@ -140,42 +158,90 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
end
context 'when processing via the default error handler' do
- include_examples 'Sidekiq arguments', args_in_job_hash: true
+ context 'with Raven events' do
+ let(:event) { raven_event}
+
+ include_examples 'Sidekiq arguments', args_in_job_hash: true
+ end
+
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ include_examples 'Sidekiq arguments', args_in_job_hash: true
+ end
end
context 'when processing via Gitlab::ErrorTracking' do
- include_examples 'Sidekiq arguments', args_in_job_hash: false
- end
+ context 'with Raven events' do
+ let(:event) { raven_event}
- context 'when a jobstr field is present' do
- let(:value) do
- {
- job: { 'args' => [1] },
- jobstr: { 'args' => [1] }.to_json
- }
+ include_examples 'Sidekiq arguments', args_in_job_hash: false
end
- it 'removes the jobstr' do
- expect(result_hash.dig(:extra, :sidekiq)).to eq(value.except(:jobstr))
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ include_examples 'Sidekiq arguments', args_in_job_hash: false
end
end
- context 'when no jobstr value is present' do
- let(:value) { { job: { 'args' => [1] } } }
+ shared_examples 'handles jobstr fields' do
+ context 'when a jobstr field is present' do
+ let(:value) do
+ {
+ job: { 'args' => [1] },
+ jobstr: { 'args' => [1] }.to_json
+ }
+ end
+
+ it 'removes the jobstr' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value.except(:jobstr))
+ end
+ end
+
+ context 'when no jobstr value is present' do
+ let(:value) { { job: { 'args' => [1] } } }
- it 'does nothing' do
- expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ it 'does nothing' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ end
end
end
+
+ context 'with Raven events' do
+ let(:event) { raven_event}
+
+ it_behaves_like 'handles jobstr fields'
+ end
+
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ it_behaves_like 'handles jobstr fields'
+ end
end
context 'when there is no Sidekiq data' do
let(:value) { { tags: { foo: 'bar', baz: 'quux' } } }
let(:wrapped_value) { value }
- it 'does nothing' do
- expect(result_hash).to include(value)
- expect(result_hash.dig(:extra, :sidekiq)).to be_nil
+ shared_examples 'does nothing' do
+ it 'does nothing' do
+ expect(result_hash).to include(value)
+ expect(result_hash.dig(:extra, :sidekiq)).to be_nil
+ end
+ end
+
+ context 'with Raven events' do
+ let(:event) { raven_event}
+
+ it_behaves_like 'does nothing'
+ end
+
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ it_behaves_like 'does nothing'
end
end
@@ -183,8 +249,22 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
let(:value) { { other: 'foo' } }
let(:wrapped_value) { { extra: { sidekiq: value } } }
- it 'does nothing' do
- expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ shared_examples 'does nothing' do
+ it 'does nothing' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ end
+ end
+
+ context 'with Raven events' do
+ let(:event) { raven_event}
+
+ it_behaves_like 'does nothing'
+ end
+
+ context 'with Sentry events' do
+ let(:event) { sentry_event}
+
+ it_behaves_like 'does nothing'
end
end
end
diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb
index a5d44963f4b..936954fc1b6 100644
--- a/spec/lib/gitlab/error_tracking_spec.rb
+++ b/spec/lib/gitlab/error_tracking_spec.rb
@@ -3,13 +3,14 @@
require 'spec_helper'
require 'raven/transports/dummy'
+require 'sentry/transport/dummy_transport'
RSpec.describe Gitlab::ErrorTracking do
let(:exception) { RuntimeError.new('boom') }
let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' }
let(:extra) { { issue_url: issue_url, some_other_info: 'info' } }
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
let(:sentry_payload) do
{
@@ -43,17 +44,28 @@ RSpec.describe Gitlab::ErrorTracking do
}
end
- let(:sentry_event) { Gitlab::Json.parse(Raven.client.transport.events.last[1]) }
+ let(:raven_event) do
+ event = Raven.client.transport.events.last[1]
+ Gitlab::Json.parse(event)
+ end
+
+ let(:sentry_event) do
+ Sentry.get_current_client.transport.events.last
+ end
before do
+ stub_feature_flags(enable_old_sentry_integration: true)
+ stub_feature_flags(enable_new_sentry_integration: true)
stub_sentry_settings
- allow(described_class).to receive(:sentry_dsn).and_return(Gitlab.config.sentry.dsn)
+ allow(described_class).to receive(:sentry_configurable?) { true }
+
allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid')
allow(I18n).to receive(:locale).and_return('en')
described_class.configure do |config|
- config.encoding = 'json'
+ config.encoding = 'json' if config.respond_to?(:encoding=)
+ config.transport.transport_class = Sentry::DummyTransport if config.respond_to?(:transport)
end
end
@@ -63,6 +75,10 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
+ after do
+ Sentry.get_current_scope.clear
+ end
+
describe '.track_and_raise_for_dev_exception' do
context 'when exceptions for dev should be raised' do
before do
@@ -71,6 +87,7 @@ RSpec.describe Gitlab::ErrorTracking do
it 'raises the exception' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
+ expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
expect do
described_class.track_and_raise_for_dev_exception(
@@ -89,6 +106,7 @@ RSpec.describe Gitlab::ErrorTracking do
it 'logs the exception with all attributes passed' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
+ expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
described_class.track_and_raise_for_dev_exception(
exception,
@@ -112,6 +130,7 @@ RSpec.describe Gitlab::ErrorTracking do
describe '.track_and_raise_exception' do
it 'always raises the exception' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
+ expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
expect do
described_class.track_and_raise_for_dev_exception(
@@ -136,20 +155,24 @@ RSpec.describe Gitlab::ErrorTracking do
end
describe '.track_exception' do
- subject(:track_exception) { described_class.track_exception(exception, extra) }
+ subject(:track_exception) do
+ described_class.track_exception(exception, extra)
+ end
before do
allow(Raven).to receive(:capture_exception).and_call_original
+ allow(Sentry).to receive(:capture_exception).and_call_original
allow(Gitlab::ErrorTracking::Logger).to receive(:error)
end
it 'calls Raven.capture_exception' do
track_exception
- expect(Raven).to have_received(:capture_exception).with(
- exception,
- sentry_payload
- )
+ expect(Raven)
+ .to have_received(:capture_exception).with(exception, sentry_payload)
+
+ expect(Sentry)
+ .to have_received(:capture_exception).with(exception, sentry_payload)
end
it 'calls Gitlab::ErrorTracking::Logger.error with formatted payload' do
@@ -172,7 +195,10 @@ RSpec.describe Gitlab::ErrorTracking do
context 'the exception implements :sentry_extra_data' do
let(:extra_info) { { event: 'explosion', size: :massive } }
- let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info, backtrace: caller, cause: nil) }
+
+ before do
+ allow(exception).to receive(:sentry_extra_data).and_return(extra_info)
+ end
it 'includes the extra data from the exception in the tracking information' do
track_exception
@@ -180,29 +206,30 @@ RSpec.describe Gitlab::ErrorTracking do
expect(Raven).to have_received(:capture_exception).with(
exception, a_hash_including(extra: a_hash_including(extra_info))
)
+
+ expect(Sentry).to have_received(:capture_exception).with(
+ exception, a_hash_including(extra: a_hash_including(extra_info))
+ )
end
end
context 'the exception implements :sentry_extra_data, which returns nil' do
- let(:exception) { double(message: 'bang!', sentry_extra_data: nil, backtrace: caller, cause: nil) }
let(:extra) { { issue_url: issue_url } }
+ before do
+ allow(exception).to receive(:sentry_extra_data).and_return(nil)
+ end
+
it 'just includes the other extra info' do
track_exception
expect(Raven).to have_received(:capture_exception).with(
exception, a_hash_including(extra: a_hash_including(extra))
)
- end
- end
-
- context 'when the error is kind of an `ActiveRecord::StatementInvalid`' do
- let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }
- it 'injects the normalized sql query into extra' do
- track_exception
-
- expect(sentry_event.dig('extra', 'sql')).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
+ expect(Sentry).to have_received(:capture_exception).with(
+ exception, a_hash_including(extra: a_hash_including(extra))
+ )
end
end
end
@@ -212,32 +239,65 @@ RSpec.describe Gitlab::ErrorTracking do
before do
allow(Raven).to receive(:capture_exception).and_call_original
+ allow(Sentry).to receive(:capture_exception).and_call_original
allow(Gitlab::ErrorTracking::Logger).to receive(:error)
end
context 'custom GitLab context when using Raven.capture_exception directly' do
- subject(:raven_capture_exception) { Raven.capture_exception(exception) }
+ subject(:track_exception) { Raven.capture_exception(exception) }
it 'merges a default set of tags into the existing tags' do
allow(Raven.context).to receive(:tags).and_return(foo: 'bar')
- raven_capture_exception
+ track_exception
- expect(sentry_event['tags']).to include('correlation_id', 'feature_category', 'foo', 'locale', 'program')
+ expect(raven_event['tags']).to include('correlation_id', 'feature_category', 'foo', 'locale', 'program')
end
it 'merges the current user information into the existing user information' do
Raven.user_context(id: -1)
- raven_capture_exception
+ track_exception
- expect(sentry_event['user']).to eq('id' => -1, 'username' => user.username)
+ expect(raven_event['user']).to eq('id' => -1, 'username' => user.username)
+ end
+ end
+
+ context 'custom GitLab context when using Sentry.capture_exception directly' do
+ subject(:track_exception) { Sentry.capture_exception(exception) }
+
+ it 'merges a default set of tags into the existing tags' do
+ Sentry.set_tags(foo: 'bar')
+
+ track_exception
+
+ expect(sentry_event.tags).to include(:correlation_id, :feature_category, :foo, :locale, :program)
+ end
+
+ it 'merges the current user information into the existing user information' do
+ Sentry.set_user(id: -1)
+
+ track_exception
+
+ expect(sentry_event.user).to eq(id: -1, username: user.username)
end
end
context 'with sidekiq args' do
context 'when the args does not have anything sensitive' do
- let(:extra) { { sidekiq: { 'class' => 'PostReceive', 'args' => [1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'] } } }
+ let(:extra) do
+ {
+ sidekiq: {
+ 'class' => 'PostReceive',
+ 'args' => [
+ 1,
+ { 'id' => 2, 'name' => 'hello' },
+ 'some-value',
+ 'another-value'
+ ]
+ }
+ }
+ end
it 'ensures extra.sidekiq.args is a string' do
track_exception
@@ -254,8 +314,10 @@ RSpec.describe Gitlab::ErrorTracking do
it 'does not filter parameters when sending to Sentry' do
track_exception
+ expected_data = [1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value']
- expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq([1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'])
+ expect(raven_event.dig('extra', 'sidekiq', 'args')).to eq(expected_data)
+ expect(sentry_event.extra[:sidekiq]['args']).to eq(expected_data)
end
end
@@ -265,7 +327,8 @@ RSpec.describe Gitlab::ErrorTracking do
it 'filters sensitive arguments before sending and logging' do
track_exception
- expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2])
+ expect(raven_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2])
+ expect(sentry_event.extra[:sidekiq]['args']).to eq(['[FILTERED]', 1, 2])
expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with(
hash_including(
'extra.sidekiq' => {
@@ -285,8 +348,10 @@ RSpec.describe Gitlab::ErrorTracking do
it 'sets the GRPC debug error string in the Sentry event and adds a custom fingerprint' do
track_exception
- expect(sentry_event.dig('extra', 'grpc_debug_error_string')).to eq('{"hello":1}')
- expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
+ expect(raven_event.dig('extra', 'grpc_debug_error_string')).to eq('{"hello":1}')
+ expect(raven_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
+ expect(sentry_event.extra[:grpc_debug_error_string]).to eq('{"hello":1}')
+ expect(sentry_event.fingerprint).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
end
end
@@ -296,8 +361,10 @@ RSpec.describe Gitlab::ErrorTracking do
it 'does not do any processing on the event' do
track_exception
- expect(sentry_event['extra']).not_to include('grpc_debug_error_string')
- expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
+ expect(raven_event['extra']).not_to include('grpc_debug_error_string')
+ expect(raven_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
+ expect(sentry_event.extra).not_to include(:grpc_debug_error_string)
+ expect(sentry_event.fingerprint).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
end
end
end
diff --git a/spec/lib/gitlab/fips_spec.rb b/spec/lib/gitlab/fips_spec.rb
index 2ede2e3adf3..4d19a44f617 100644
--- a/spec/lib/gitlab/fips_spec.rb
+++ b/spec/lib/gitlab/fips_spec.rb
@@ -6,16 +6,46 @@ RSpec.describe Gitlab::FIPS do
describe ".enabled?" do
subject { described_class.enabled? }
- context "feature flag is enabled" do
- it { is_expected.to be_truthy }
+ let(:openssl_fips_mode) { false }
+ let(:fips_mode_env_var) { nil }
+
+ before do
+ expect(OpenSSL).to receive(:fips_mode).and_return(openssl_fips_mode)
+ stub_env("FIPS_MODE", fips_mode_env_var)
+ end
+
+ describe "OpenSSL auto-detection" do
+ context "OpenSSL is in FIPS mode" do
+ let(:openssl_fips_mode) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context "OpenSSL is not in FIPS mode" do
+ let(:openssl_fips_mode) { false }
+
+ it { is_expected.to be_falsey }
+ end
end
- context "feature flag is disabled" do
- before do
- stub_feature_flags(fips_mode: false)
+ describe "manual configuration via env var" do
+ context "env var is not set" do
+ let(:fips_mode_env_var) { nil }
+
+ it { is_expected.to be_falsey }
end
- it { is_expected.to be_falsey }
+ context "env var is set to true" do
+ let(:fips_mode_env_var) { "true" }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context "env var is set to false" do
+ let(:fips_mode_env_var) { "false" }
+
+ it { is_expected.to be_falsey }
+ end
end
end
end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index e27fee9286b..c8d86edc55f 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -1255,7 +1255,7 @@ RSpec.describe ContainerRepository, :aggregate_failures do
subject { described_class.ready_for_import }
before do
- stub_application_setting(container_registry_import_target_plan: project.namespace.actual_plan_name)
+ stub_application_setting(container_registry_import_target_plan: root_group.actual_plan_name)
end
it 'works' do
diff --git a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
index 0e7230c042e..67d664a617b 100644
--- a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
+++ b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
@@ -102,5 +102,81 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
is_expected.to eq(destroyed_artifacts_count: 0, statistics_updates: {}, status: :success)
end
end
+
+ context 'with artifacts that has backfilled expire_at' do
+ let!(:created_on_00_30_45_minutes_on_21_22_23) do
+ [
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-21 00:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-21 01:30:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-22 12:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-22 12:30:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-23 23:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-23 23:30:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-23 06:45:00.000'))
+ ]
+ end
+
+ let!(:created_close_to_00_or_30_minutes) do
+ [
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-21 00:00:00.001')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-21 00:30:00.999'))
+ ]
+ end
+
+ let!(:created_on_00_or_30_minutes_on_other_dates) do
+ [
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-01 00:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-19 12:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-24 23:30:00.000'))
+ ]
+ end
+
+ let!(:created_at_other_times) do
+ [
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-19 00:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-19 00:30:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-24 00:00:00.000')),
+ create(:ci_job_artifact, expire_at: Time.zone.parse('2022-01-24 00:30:00.000'))
+ ]
+ end
+
+ let(:artifacts_to_keep) { created_on_00_30_45_minutes_on_21_22_23 }
+ let(:artifacts_to_delete) { created_close_to_00_or_30_minutes + created_on_00_or_30_minutes_on_other_dates + created_at_other_times }
+ let(:all_artifacts) { artifacts_to_keep + artifacts_to_delete }
+
+ let(:artifacts) { Ci::JobArtifact.where(id: all_artifacts.map(&:id)) }
+
+ it 'deletes job artifacts that do not have expire_at on 00, 30 or 45 minute of 21, 22, 23 of the month' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(artifacts_to_delete.size * -1)
+ end
+
+ it 'keeps job artifacts that have expire_at on 00, 30 or 45 minute of 21, 22, 23 of the month' do
+ expect { subject }.not_to change { Ci::JobArtifact.where(id: artifacts_to_keep.map(&:id)).count }
+ end
+
+ it 'removes expire_at on job artifacts that have expire_at on 00, 30 or 45 minute of 21, 22, 23 of the month' do
+ subject
+
+ expect(artifacts_to_keep.all? { |artifact| artifact.reload.expire_at.nil? }).to be(true)
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_detect_wrongly_expired_artifacts: false)
+ end
+
+ it 'deletes all job artifacts' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(all_artifacts.size * -1)
+ end
+ end
+
+ context 'when fix_expire_at is false' do
+ let(:service) { described_class.new(artifacts, pick_up_at: Time.current, fix_expire_at: false) }
+
+ it 'deletes all job artifacts' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(all_artifacts.size * -1)
+ end
+ end
+ end
end
end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 8c60dc30cdb..20f46396424 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -90,10 +90,18 @@ module StubConfiguration
allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages))
end
- def stub_sentry_settings
- allow(Gitlab.config.sentry).to receive(:enabled).and_return(true)
- allow(Gitlab.config.sentry).to receive(:dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42')
- allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/43')
+ def stub_sentry_settings(enabled: true)
+ allow(Gitlab.config.sentry).to receive(:enabled) { enabled }
+ allow(Gitlab::CurrentSettings).to receive(:sentry_enabled?) { enabled }
+
+ dsn = 'dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42'
+ allow(Gitlab.config.sentry).to receive(:dsn) { dsn }
+ allow(Gitlab::CurrentSettings).to receive(:sentry_dsn) { dsn }
+
+ clientside_dsn = 'dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/43'
+ allow(Gitlab.config.sentry).to receive(:clientside_dsn) { clientside_dsn }
+ allow(Gitlab::CurrentSettings)
+ .to receive(:sentry_clientside_dsn) { clientside_dsn }
end
def stub_kerberos_setting(messages)
diff --git a/spec/support/sentry.rb b/spec/support/sentry.rb
new file mode 100644
index 00000000000..c439b6c0fd9
--- /dev/null
+++ b/spec/support/sentry.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ config.around(:example, :sentry) do |example|
+ dsn = Sentry.get_current_client.configuration.dsn
+ Sentry.get_current_client.configuration.dsn = 'dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42'
+ begin
+ example.run
+ ensure
+ Sentry.get_current_client.configuration.dsn = dsn.to_s.presence
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/container_repositories_shared_context.rb b/spec/support/shared_contexts/container_repositories_shared_context.rb
index 7f61631dce0..9a9f80a3cbd 100644
--- a/spec/support/shared_contexts/container_repositories_shared_context.rb
+++ b/spec/support/shared_contexts/container_repositories_shared_context.rb
@@ -1,13 +1,16 @@
# frozen_string_literal: true
RSpec.shared_context 'importable repositories' do
- let_it_be(:project) { create(:project) }
+ let_it_be(:root_group) { create(:group) }
+ let_it_be(:group) { create(:group, parent_id: root_group.id) }
+ let_it_be(:project) { create(:project, namespace: group) }
let_it_be(:valid_container_repository) { create(:container_repository, project: project, created_at: 2.days.ago) }
let_it_be(:valid_container_repository2) { create(:container_repository, project: project, created_at: 1.year.ago) }
let_it_be(:importing_container_repository) { create(:container_repository, :importing, project: project, created_at: 2.days.ago) }
let_it_be(:new_container_repository) { create(:container_repository, project: project) }
- let_it_be(:denied_group) { create(:group) }
+ let_it_be(:denied_root_group) { create(:group) }
+ let_it_be(:denied_group) { create(:group, parent_id: denied_root_group.id) }
let_it_be(:denied_project) { create(:project, group: denied_group) }
let_it_be(:denied_container_repository) { create(:container_repository, project: denied_project, created_at: 2.days.ago) }
@@ -21,7 +24,7 @@ RSpec.shared_context 'importable repositories' do
Feature::FlipperGate.create!(
feature_key: 'container_registry_phase_2_deny_list',
key: 'actors',
- value: "Group:#{denied_group.id}"
+ value: "Group:#{denied_root_group.id}"
)
end
end
diff --git a/spec/views/projects/empty.html.haml_spec.rb b/spec/views/projects/empty.html.haml_spec.rb
index 416dfc10174..6077dda3c98 100644
--- a/spec/views/projects/empty.html.haml_spec.rb
+++ b/spec/views/projects/empty.html.haml_spec.rb
@@ -25,6 +25,21 @@ RSpec.describe 'projects/empty' do
expect(rendered).to have_content("git clone")
end
+
+ context 'when default branch name contains special shell characters' do
+ let(:branch_name) { ';rm -rf /' }
+
+ before do
+ allow(project).to receive(:default_branch_or_main).and_return(branch_name)
+ end
+
+ it 'escapes the default branch name' do
+ render
+
+ expect(rendered).not_to have_content(branch_name)
+ expect(rendered).to have_content(branch_name.shellescape)
+ end
+ end
end
context 'when user can not push code on the project' do