From d18b7dc5eea84db5008986c6879a24ad7f6462a6 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 10 Mar 2022 18:09:14 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/features/boards/boards_spec.rb | 1 + .../user_sees_avatar_on_diff_notes_spec.rb | 1 + .../features/search/user_searches_for_code_spec.rb | 8 +- .../search/user_searches_for_issues_spec.rb | 2 +- .../user_searches_for_merge_requests_spec.rb | 2 +- .../search/user_searches_for_milestones_spec.rb | 4 +- .../search/user_searches_for_wiki_pages_spec.rb | 2 +- .../__snapshots__/settings_form_spec.js.snap | 2 +- .../settings/components/settings_form_spec.js | 26 ++-- .../project/settings/graphql/cache_updated_spec.js | 25 ++- .../learn_gitlab/components/learn_gitlab_spec.js | 12 -- spec/frontend/search/topbar/components/app_spec.js | 33 +--- .../components/training_provider_list_spec.js | 7 +- spec/frontend/security_configuration/mock_data.js | 12 +- .../user_avatar/user_avatar_image_spec.js | 172 ++++++++++++++++----- spec/lib/api/entities/ci/job_artifact_file_spec.rb | 18 +++ .../api/entities/ci/job_request/dependency_spec.rb | 27 ++++ .../processor/grpc_error_processor_spec.rb | 142 ++++++++++++----- .../processor/sidekiq_processor_spec.rb | 126 ++++++++++++--- spec/lib/gitlab/error_tracking_spec.rb | 131 ++++++++++++---- spec/lib/gitlab/fips_spec.rb | 42 ++++- spec/models/container_repository_spec.rb | 2 +- .../ci/job_artifacts/destroy_batch_service_spec.rb | 76 +++++++++ spec/support/helpers/stub_configuration.rb | 16 +- spec/support/sentry.rb | 13 ++ .../container_repositories_shared_context.rb | 9 +- spec/views/projects/empty.html.haml_spec.rb | 15 ++ 27 files changed, 698 insertions(+), 228 deletions(-) create mode 100644 spec/lib/api/entities/ci/job_artifact_file_spec.rb create mode 100644 spec/lib/api/entities/ci/job_request/dependency_spec.rb create mode 100644 spec/support/sentry.rb (limited to 'spec') 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 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 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 -- cgit v1.2.3