diff options
Diffstat (limited to 'spec')
39 files changed, 518 insertions, 338 deletions
diff --git a/spec/features/users/active_sessions_spec.rb b/spec/features/users/active_sessions_spec.rb index e2ee78a7cc5..1605073acda 100644 --- a/spec/features/users/active_sessions_spec.rb +++ b/spec/features/users/active_sessions_spec.rb @@ -4,25 +4,29 @@ require 'spec_helper' RSpec.describe 'Active user sessions', :clean_gitlab_redis_sessions do it 'successful login adds a new active user login' do + user = create(:user) + now = Time.zone.parse('2018-03-12 09:06') - Timecop.freeze(now) do - user = create(:user) + travel_to(now) do gitlab_sign_in(user) expect(page).to have_current_path root_path, ignore_query: true sessions = ActiveSession.list(user) expect(sessions.count).to eq 1 + gitlab_sign_out + end - # refresh the current page updates the updated_at - Timecop.freeze(now + 1.minute) do - visit current_path + # refresh the current page updates the updated_at + travel_to(now + 1.minute) do + gitlab_sign_in(user) + + visit current_path - sessions = ActiveSession.list(user) - expect(sessions.first).to have_attributes( - created_at: Time.zone.parse('2018-03-12 09:06'), - updated_at: Time.zone.parse('2018-03-12 09:07') - ) - end + sessions = ActiveSession.list(user) + expect(sessions.first).to have_attributes( + created_at: Time.zone.parse('2018-03-12 09:06'), + updated_at: Time.zone.parse('2018-03-12 09:07') + ) end end diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 3c26fa97338..0cdab747a8d 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -2,8 +2,8 @@ import { GlFilteredSearchToken } from '@gitlab/ui'; import { keyBy } from 'lodash'; import { ListType } from '~/boards/constants'; import { - OPERATOR_IS_AND_IS_NOT, - OPERATOR_IS_ONLY, + OPERATORS_IS_NOT, + OPERATORS_IS, TOKEN_TITLE_ASSIGNEE, TOKEN_TITLE_AUTHOR, TOKEN_TITLE_LABEL, @@ -747,7 +747,7 @@ export const mockConfidentialToken = { title: 'Confidential', unique: true, token: GlFilteredSearchToken, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, options: [ { icon: 'eye-slash', value: 'yes', title: 'Yes' }, { icon: 'eye', value: 'no', title: 'No' }, @@ -759,7 +759,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI icon: 'user', title: TOKEN_TITLE_ASSIGNEE, type: TOKEN_TYPE_ASSIGNEE, - operators: OPERATOR_IS_AND_IS_NOT, + operators: OPERATORS_IS_NOT, token: AuthorToken, unique: true, fetchAuthors, @@ -769,7 +769,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI icon: 'pencil', title: TOKEN_TITLE_AUTHOR, type: TOKEN_TYPE_AUTHOR, - operators: OPERATOR_IS_AND_IS_NOT, + operators: OPERATORS_IS_NOT, symbol: '@', token: AuthorToken, unique: true, @@ -780,7 +780,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI icon: 'labels', title: TOKEN_TITLE_LABEL, type: TOKEN_TYPE_LABEL, - operators: OPERATOR_IS_AND_IS_NOT, + operators: OPERATORS_IS_NOT, token: LabelToken, unique: false, symbol: '~', diff --git a/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js b/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js index d3c7ea50f9d..3dce5a509ca 100644 --- a/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js +++ b/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js @@ -7,7 +7,7 @@ import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import TagToken, { TAG_SUGGESTIONS_PATH } from '~/ci/runner/components/search_tokens/tag_token.vue'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; import { getRecentlyUsedSuggestions } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; jest.mock('~/flash'); @@ -42,7 +42,7 @@ const mockTagTokenConfig = { type: 'tag', token: TagToken, recentSuggestionsStorageKey: mockStorageKey, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, }; describe('TagToken', () => { diff --git a/spec/frontend/fixtures/freeze_period.rb b/spec/frontend/fixtures/freeze_period.rb index 5aa466ef015..a1c7564d36e 100644 --- a/spec/frontend/fixtures/freeze_period.rb +++ b/spec/frontend/fixtures/freeze_period.rb @@ -13,15 +13,6 @@ RSpec.describe 'Freeze Periods (JavaScript fixtures)' do remove_repository(project) end - around do |example| - freeze_time do - # Mock time to sept 19 (intl. talk like a pirate day) - travel_to(Time.utc(2020, 9, 19)) - - example.run - end - end - describe API::FreezePeriods, '(JavaScript fixtures)', type: :request do include ApiHelpers diff --git a/spec/frontend/issues/list/mock_data.js b/spec/frontend/issues/list/mock_data.js index 62fcbf7aad0..72d3fe93745 100644 --- a/spec/frontend/issues/list/mock_data.js +++ b/spec/frontend/issues/list/mock_data.js @@ -1,7 +1,7 @@ import { FILTERED_SEARCH_TERM, OPERATOR_IS, - OPERATOR_IS_NOT, + OPERATOR_NOT, OPERATOR_OR, TOKEN_TYPE_ASSIGNEE, TOKEN_TYPE_AUTHOR, @@ -184,41 +184,41 @@ export const locationSearchWithSpecialValues = [ export const filteredTokens = [ { type: TOKEN_TYPE_AUTHOR, value: { data: 'homer', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_AUTHOR, value: { data: 'marge', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_AUTHOR, value: { data: 'marge', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'bart', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'lisa', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_ASSIGNEE, value: { data: '5', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'patty', operator: OPERATOR_IS_NOT } }, - { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'selma', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'patty', operator: OPERATOR_NOT } }, + { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'selma', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'carl', operator: OPERATOR_OR } }, { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'lenny', operator: OPERATOR_OR } }, { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 3', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 4', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 20', operator: OPERATOR_IS_NOT } }, - { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 30', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 20', operator: OPERATOR_NOT } }, + { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 30', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_LABEL, value: { data: 'cartoon', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_LABEL, value: { data: 'tv', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_LABEL, value: { data: 'live action', operator: OPERATOR_IS_NOT } }, - { type: TOKEN_TYPE_LABEL, value: { data: 'drama', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_LABEL, value: { data: 'live action', operator: OPERATOR_NOT } }, + { type: TOKEN_TYPE_LABEL, value: { data: 'drama', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_RELEASE, value: { data: 'v3', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_RELEASE, value: { data: 'v4', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_RELEASE, value: { data: 'v20', operator: OPERATOR_IS_NOT } }, - { type: TOKEN_TYPE_RELEASE, value: { data: 'v30', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_RELEASE, value: { data: 'v20', operator: OPERATOR_NOT } }, + { type: TOKEN_TYPE_RELEASE, value: { data: 'v30', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_TYPE, value: { data: 'issue', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_TYPE, value: { data: 'feature', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_TYPE, value: { data: 'bug', operator: OPERATOR_IS_NOT } }, - { type: TOKEN_TYPE_TYPE, value: { data: 'incident', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_TYPE, value: { data: 'bug', operator: OPERATOR_NOT } }, + { type: TOKEN_TYPE_TYPE, value: { data: 'incident', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_MY_REACTION, value: { data: 'thumbsup', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_MY_REACTION, value: { data: 'thumbsdown', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_MY_REACTION, value: { data: 'thumbsdown', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_CONFIDENTIAL, value: { data: 'yes', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_ITERATION, value: { data: '4', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_ITERATION, value: { data: '12', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_ITERATION, value: { data: '20', operator: OPERATOR_IS_NOT } }, - { type: TOKEN_TYPE_ITERATION, value: { data: '42', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_ITERATION, value: { data: '20', operator: OPERATOR_NOT } }, + { type: TOKEN_TYPE_ITERATION, value: { data: '42', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_EPIC, value: { data: '12', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_EPIC, value: { data: '34', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_EPIC, value: { data: '34', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_WEIGHT, value: { data: '1', operator: OPERATOR_IS } }, - { type: TOKEN_TYPE_WEIGHT, value: { data: '3', operator: OPERATOR_IS_NOT } }, + { type: TOKEN_TYPE_WEIGHT, value: { data: '3', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_CONTACT, value: { data: '123', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_ORGANIZATION, value: { data: '456', operator: OPERATOR_IS } }, { type: FILTERED_SEARCH_TERM, value: { data: 'find' } }, diff --git a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js index 98bdfc3fcbc..fcdb162dfed 100644 --- a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js +++ b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js @@ -1,6 +1,6 @@ import { GlFilteredSearch } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; import JobsFilteredSearch from '~/jobs/components/filtered_search/jobs_filtered_search.vue'; import { mockFailedSearchToken } from '../../mock_data'; @@ -41,7 +41,7 @@ describe('Jobs filtered search', () => { icon: 'status', title: 'Status', unique: true, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, }); }); diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js index 8fd50bea280..69765d31674 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js @@ -8,7 +8,7 @@ import ArtifactsList from '~/packages_and_registries/harbor_registry/components/ import waitForPromises from 'helpers/wait_for_promises'; import DetailsHeader from '~/packages_and_registries/harbor_registry/components/details/details_header.vue'; import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; import { NAME_SORT_FIELD, TOKEN_TYPE_TAG_NAME, @@ -137,7 +137,7 @@ describe('Harbor Details Page', () => { title: s__('HarborRegistry|Tag'), unique: true, token: GlFilteredSearchToken, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, }, ], }); diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js index ee3eaaf5ef3..e5ad735bf66 100644 --- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js +++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js @@ -6,7 +6,7 @@ import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import Api from '~/api'; import axios from '~/lib/utils/axios_utils'; import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; import { TRACKING_CATEGORIES } from '~/pipelines/constants'; import { users, mockSearch, branches, tags } from '../mock_data'; @@ -63,7 +63,7 @@ describe('Pipelines filtered search', () => { title: 'Trigger author', unique: true, projectId: '21', - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, }); expect(findBranchToken()).toMatchObject({ @@ -73,7 +73,7 @@ describe('Pipelines filtered search', () => { unique: true, projectId: '21', defaultBranchName: 'main', - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, }); expect(findSourceToken()).toMatchObject({ @@ -81,7 +81,7 @@ describe('Pipelines filtered search', () => { icon: 'trigger-source', title: 'Source', unique: true, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, }); expect(findStatusToken()).toMatchObject({ @@ -89,7 +89,7 @@ describe('Pipelines filtered search', () => { icon: 'status', title: 'Status', unique: true, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, }); expect(findTagToken()).toMatchObject({ @@ -97,7 +97,7 @@ describe('Pipelines filtered search', () => { icon: 'tag', title: 'Tag name', unique: true, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js index 06ee017dee7..270a37f87e7 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js @@ -1,9 +1,28 @@ -import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { shallowMount, mount } from '@vue/test-utils'; +import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; +import waitForPromises from 'helpers/wait_for_promises'; + +import api from '~/api'; + +import showGlobalToast from '~/vue_shared/plugins/global_toast'; + import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue'; import MrWidgetAuthorTime from '~/vue_merge_request_widget/components/mr_widget_author_time.vue'; import StateContainer from '~/vue_merge_request_widget/components/state_container.vue'; +import Actions from '~/vue_merge_request_widget/components/action_buttons.vue'; + +import { MR_WIDGET_CLOSED_REOPEN_FAILURE } from '~/vue_merge_request_widget/i18n'; + +jest.mock('~/api', () => ({ + updateMergeRequest: jest.fn(), +})); +jest.mock('~/vue_shared/plugins/global_toast'); + +useMockLocationHelper(); const MOCK_DATA = { + iid: 1, metrics: { mergedBy: {}, closedBy: { @@ -19,22 +38,39 @@ const MOCK_DATA = { }, targetBranchPath: '/twitter/flight/commits/so_long_jquery', targetBranch: 'so_long_jquery', + targetProjectId: 'twitter/flight', }; +function createComponent({ shallow = true, props = {} } = {}) { + const mounter = shallow ? shallowMount : mount; + + return mounter(closedComponent, { + propsData: { + mr: MOCK_DATA, + ...props, + }, + }); +} + +function findActions(wrapper) { + return wrapper.findComponent(StateContainer).findComponent(Actions); +} + +function findReopenActionButton(wrapper) { + return findActions(wrapper).find('button[data-testid="extension-actions-reopen-button"]'); +} + describe('MRWidgetClosed', () => { let wrapper; beforeEach(() => { - wrapper = shallowMount(closedComponent, { - propsData: { - mr: MOCK_DATA, - }, - }); + wrapper = createComponent(); }); afterEach(() => { - wrapper.destroy(); - wrapper = null; + if (wrapper) { + wrapper.destroy(); + } }); it('renders closed icon', () => { @@ -51,4 +87,93 @@ describe('MRWidgetClosed', () => { dateReadable: MOCK_DATA.metrics.readableClosedAt, }); }); + + describe('actions', () => { + describe('reopen', () => { + beforeEach(() => { + window.gon = { current_user_id: 1 }; + api.updateMergeRequest.mockResolvedValue(true); + wrapper = createComponent({ shallow: false }); + }); + + it('shows the "reopen" button', () => { + expect(wrapper.findComponent(StateContainer).props().actions.length).toBe(1); + expect(findReopenActionButton(wrapper).text()).toBe('Reopen'); + }); + + it('does not show widget actions when the user is not logged in', () => { + window.gon = {}; + + wrapper = createComponent(); + + expect(findActions(wrapper).exists()).toBe(false); + }); + + it('makes the reopen request with the correct MR information', async () => { + const reopenButton = findReopenActionButton(wrapper); + + reopenButton.trigger('click'); + await nextTick(); + + expect(api.updateMergeRequest).toHaveBeenCalledWith( + MOCK_DATA.targetProjectId, + MOCK_DATA.iid, + { state_event: 'reopen' }, + ); + }); + + it('shows "Reopening..." while the reopen network request is pending', async () => { + const reopenButton = findReopenActionButton(wrapper); + + api.updateMergeRequest.mockReturnValue(new Promise(() => {})); + + reopenButton.trigger('click'); + await nextTick(); + + expect(reopenButton.text()).toBe('Reopening...'); + }); + + it('shows "Refreshing..." when the reopen has succeeded', async () => { + const reopenButton = findReopenActionButton(wrapper); + + reopenButton.trigger('click'); + await waitForPromises(); + + expect(reopenButton.text()).toBe('Refreshing...'); + }); + + it('reloads the page when a reopen has succeeded', async () => { + const reopenButton = findReopenActionButton(wrapper); + + reopenButton.trigger('click'); + await waitForPromises(); + + expect(window.location.reload).toHaveBeenCalledTimes(1); + }); + + it('shows "Reopen" when a reopen request has failed', async () => { + const reopenButton = findReopenActionButton(wrapper); + + api.updateMergeRequest.mockRejectedValue(false); + + reopenButton.trigger('click'); + await waitForPromises(); + + expect(window.location.reload).not.toHaveBeenCalled(); + expect(reopenButton.text()).toBe('Reopen'); + }); + + it('requests a toast popup when a reopen request has failed', async () => { + const reopenButton = findReopenActionButton(wrapper); + + api.updateMergeRequest.mockRejectedValue(false); + + reopenButton.trigger('click'); + await waitForPromises(); + + expect(showGlobalToast).toHaveBeenCalledTimes(1); + expect(showGlobalToast).toHaveBeenCalledWith(MR_WIDGET_CLOSED_REOPEN_FAILURE); + }); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js index a6713b7e7e4..f9cc884f221 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js @@ -1,7 +1,7 @@ import { GlFilteredSearchToken } from '@gitlab/ui'; import { mockLabels } from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data'; import Api from '~/api'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import BranchToken from '~/vue_shared/components/filtered_search_bar/tokens/branch_token.vue'; import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue'; @@ -202,7 +202,7 @@ export const mockBranchToken = { title: 'Source Branch', unique: true, token: BranchToken, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, fetchBranches: Api.branches.bind(Api), }; @@ -213,7 +213,7 @@ export const mockAuthorToken = { unique: false, symbol: '@', token: AuthorToken, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, fetchPath: 'gitlab-org/gitlab-test', fetchAuthors: Api.projectUsers.bind(Api), }; @@ -225,7 +225,7 @@ export const mockLabelToken = { unique: false, symbol: '~', token: LabelToken, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, fetchLabels: () => Promise.resolve(mockLabels), }; @@ -236,7 +236,7 @@ export const mockMilestoneToken = { unique: true, symbol: '%', token: MilestoneToken, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, fetchMilestones: () => Promise.resolve({ data: mockMilestones }), }; @@ -254,7 +254,7 @@ export const mockReactionEmojiToken = { title: 'My-Reaction', unique: true, token: EmojiToken, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, fetchEmojis: () => Promise.resolve(mockEmojis), }; @@ -265,7 +265,7 @@ export const mockCrmContactToken = { token: CrmContactToken, isProject: false, fullPath: 'group', - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, unique: true, }; @@ -276,7 +276,7 @@ export const mockCrmOrganizationToken = { token: CrmOrganizationToken, isProject: false, fullPath: 'group', - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, unique: true, }; @@ -286,7 +286,7 @@ export const mockMembershipToken = { title: 'Membership', token: GlFilteredSearchToken, unique: true, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, options: [ { value: 'exclude', title: 'Direct' }, { value: 'only', title: 'Inherited' }, diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js index a0126c2bd63..5546b0c7032 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js @@ -17,7 +17,7 @@ import { import { DEFAULT_NONE_ANY, OPERATOR_IS, - OPERATOR_IS_NOT, + OPERATOR_NOT, } from '~/vue_shared/components/filtered_search_bar/constants'; import { getRecentlyUsedSuggestions, @@ -301,9 +301,9 @@ describe('BaseToken', () => { describe('with default suggestions', () => { describe.each` - operator | shouldRenderFilteredSearchSuggestion - ${OPERATOR_IS} | ${true} - ${OPERATOR_IS_NOT} | ${false} + operator | shouldRenderFilteredSearchSuggestion + ${OPERATOR_IS} | ${true} + ${OPERATOR_NOT} | ${false} `('when operator is $operator', ({ shouldRenderFilteredSearchSuggestion, operator }) => { beforeEach(() => { const props = { diff --git a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js index c0c3c4a9729..2e2d04efb55 100644 --- a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js +++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js @@ -2,7 +2,7 @@ import { GlAlert, GlBadge, GlPagination, GlTabs, GlTab } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { nextTick } from 'vue'; import Tracking from '~/tracking'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import PageWrapper from '~/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue'; @@ -293,7 +293,7 @@ describe('AlertManagementEmptyState', () => { unique: true, symbol: '@', token: AuthorToken, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, fetchPath: '/link', fetchAuthors: expect.any(Function), }, @@ -304,7 +304,7 @@ describe('AlertManagementEmptyState', () => { unique: true, symbol: '@', token: AuthorToken, - operators: OPERATOR_IS_ONLY, + operators: OPERATORS_IS, fetchPath: '/link', fetchAuthors: expect.any(Function), }, diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index a409c15533b..cbd931c514f 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -192,4 +192,8 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do include_examples "XSS prevention", "ruby" end + + it_behaves_like "filter timeout" do + let(:text) { '<pre lang="ruby"><code>def fun end</code></pre>' } + end end diff --git a/spec/lib/banzai/filter/timeout_html_pipeline_filter_spec.rb b/spec/lib/banzai/filter/timeout_html_pipeline_filter_spec.rb new file mode 100644 index 00000000000..cdb40ef5b04 --- /dev/null +++ b/spec/lib/banzai/filter/timeout_html_pipeline_filter_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Banzai::Filter::TimeoutHtmlPipelineFilter do + include FilterSpecHelper + + it_behaves_like 'filter timeout' do + let(:text) { '<p>some text</p>' } + end + + it 'raises NotImplementedError' do + expect { filter('test') }.to raise_error NotImplementedError + end + + context 'when markup_rendering_timeout is disabled' do + it 'waits until the execution completes' do + text = '<p>some text</p>' + + stub_feature_flags(markup_rendering_timeout: false) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:call_with_timeout) do + text + end + end + + expect(Gitlab::RenderTimeout).not_to receive(:timeout) + + result = filter(text) + + expect(result).to eq text + end + end +end diff --git a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb index 24f8fb40445..271022e7c55 100644 --- a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb +++ b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb @@ -22,10 +22,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do project.add_maintainer(user) mr1.metrics.update!(merged_at: 1.month.ago) mr2.metrics.update!(merged_at: Time.now) - end - - around do |example| - Timecop.freeze { example.run } + freeze_time end describe 'date range parameters' do diff --git a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb index 258f4a0d019..4db5d64164e 100644 --- a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb +++ b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb @@ -18,10 +18,6 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Median do subject { described_class.new(stage: stage, query: query).seconds } - around do |example| - Timecop.freeze { example.run } - end - it 'retruns nil when no results' do expect(subject).to eq(nil) end @@ -30,11 +26,11 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Median do merge_request1 = create(:merge_request, source_branch: '1', target_project: project, source_project: project) merge_request2 = create(:merge_request, source_branch: '2', target_project: project, source_project: project) - travel_to(5.minutes.from_now) do + travel(5.minutes) do merge_request1.metrics.update!(merged_at: Time.zone.now) end - travel_to(10.minutes.from_now) do + travel(10.minutes) do merge_request2.metrics.update!(merged_at: Time.zone.now) end diff --git a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb index 34d5158a5ab..ab5a360d908 100644 --- a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb +++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do around do |example| - Timecop.freeze { example.run } + freeze_time { example.run } end let(:params) { { from: 1.year.ago, current_user: user } } diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb index b239de841b6..84f6411eae6 100644 --- a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb +++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb @@ -22,14 +22,14 @@ RSpec.describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state end it 'resets count after specified time window' do - Timecop.freeze do + freeze_time do expect(described_class.update_and_return_ips_count(user.id, 'ip2')).to eq(1) expect(described_class.update_and_return_ips_count(user.id, 'ip3')).to eq(2) + end - travel_to(Time.now.utc + described_class.config.unique_ips_limit_time_window) do - expect(described_class.update_and_return_ips_count(user.id, 'ip4')).to eq(1) - expect(described_class.update_and_return_ips_count(user.id, 'ip5')).to eq(2) - end + travel_to(Time.now.utc + described_class.config.unique_ips_limit_time_window) do + expect(described_class.update_and_return_ips_count(user.id, 'ip4')).to eq(1) + expect(described_class.update_and_return_ips_count(user.id, 'ip5')).to eq(2) end end end diff --git a/spec/lib/gitlab/checks/timed_logger_spec.rb b/spec/lib/gitlab/checks/timed_logger_spec.rb index 6c488212eca..261fdd6c002 100644 --- a/spec/lib/gitlab/checks/timed_logger_spec.rb +++ b/spec/lib/gitlab/checks/timed_logger_spec.rb @@ -17,38 +17,44 @@ RSpec.describe Gitlab::Checks::TimedLogger do logger.append_message("Checking ref: #{ref}") end + around do |example| + freeze_time do + example.run + end + end + describe '#log_timed' do it 'logs message' do - Timecop.freeze(start + 30.seconds) do - logger.log_timed(log_messages[:foo], start) { bar_check } - end + travel_to(start + 30.seconds) + + logger.log_timed(log_messages[:foo], start) { bar_check } expect(logger.full_message).to eq("Checking ref: bar\nFoo message... (30000.0ms)") end context 'when time limit was reached' do it 'cancels action' do - Timecop.freeze(start + 50.seconds) do - expect do - logger.log_timed(log_messages[:foo], start) do - bar_check - end - end.to raise_error(described_class::TimeoutError) - end + travel_to(start + 50.seconds) + + expect do + logger.log_timed(log_messages[:foo], start) do + bar_check + end + end.to raise_error(described_class::TimeoutError) expect(logger.full_message).to eq("Checking ref: bar\nFoo message... (cancelled)") end it 'cancels action with time elapsed if work was performed' do - Timecop.freeze(start + 30.seconds) do - expect do - logger.log_timed(log_messages[:foo], start) do - grpc_check - end - end.to raise_error(described_class::TimeoutError) - - expect(logger.full_message).to eq("Checking ref: bar\nFoo message... (cancelled after 30000.0ms)") - end + travel_to(start + 30.seconds) + + expect do + logger.log_timed(log_messages[:foo], start) do + grpc_check + end + end.to raise_error(described_class::TimeoutError) + + expect(logger.full_message).to eq("Checking ref: bar\nFoo message... (cancelled after 30000.0ms)") end end end diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb index 0e7d7f1efda..92ffeee8509 100644 --- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb @@ -29,8 +29,8 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do context 'when from date is given' do before do - Timecop.freeze(5.days.ago) { create(:issue, project: project) } - Timecop.freeze(5.days.from_now) { create(:issue, project: project) } + travel_to(5.days.ago) { create(:issue, project: project) } + travel_to(5.days.from_now) { create(:issue, project: project) } end it "finds the number of issues created after the 'from date'" do @@ -45,15 +45,15 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do end it "doesn't find issues from other projects" do - Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) } + travel_to(5.days.from_now) { create(:issue, project: create(:project)) } expect(subject[:value]).to eq('-') end context 'when `to` parameter is given' do before do - Timecop.freeze(5.days.ago) { create(:issue, project: project) } - Timecop.freeze(5.days.from_now) { create(:issue, project: project) } + travel_to(5.days.ago) { create(:issue, project: project) } + travel_to(5.days.from_now) { create(:issue, project: project) } end it "doesn't find any record" do @@ -78,8 +78,8 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do context 'when from date is given' do before do - Timecop.freeze(5.days.ago) { create_commit("Test message", project, user, 'master') } - Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master') } + travel_to(5.days.ago) { create_commit("Test message", project, user, 'master') } + travel_to(5.days.from_now) { create_commit("Test message", project, user, 'master') } end it "finds the number of commits created after the 'from date'" do @@ -94,21 +94,21 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do end it "doesn't find commits from other projects" do - Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project, :repository), user, 'master') } + travel_to(5.days.from_now) { create_commit("Test message", create(:project, :repository), user, 'master') } expect(subject[:value]).to eq('-') end it "finds a large (> 100) number of commits if present" do - Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master', count: 100) } + travel_to(5.days.from_now) { create_commit("Test message", project, user, 'master', count: 100) } expect(subject[:value]).to eq('100') end context 'when `to` parameter is given' do before do - Timecop.freeze(5.days.ago) { create_commit("Test message", project, user, 'master') } - Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master') } + travel_to(5.days.ago) { create_commit("Test message", project, user, 'master') } + travel_to(5.days.from_now) { create_commit("Test message", project, user, 'master') } end it "doesn't find any record" do diff --git a/spec/lib/gitlab/git/cross_repo_comparer_spec.rb b/spec/lib/gitlab/git/cross_repo_comparer_spec.rb deleted file mode 100644 index 7888e224d59..00000000000 --- a/spec/lib/gitlab/git/cross_repo_comparer_spec.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Git::CrossRepoComparer do - let(:source_project) { create(:project, :repository) } - let(:target_project) { create(:project, :repository) } - - let(:source_repo) { source_project.repository.raw_repository } - let(:target_repo) { target_project.repository.raw_repository } - - let(:source_branch) { 'feature' } - let(:target_branch) { 'master' } - let(:straight) { false } - - let(:source_commit) { source_repo.commit(source_branch) } - let(:target_commit) { source_repo.commit(target_branch) } - - subject(:result) { described_class.new(source_repo, target_repo).compare(source_branch, target_branch, straight: straight) } - - describe '#compare' do - context 'within a single repository' do - let(:target_project) { source_project } - - context 'a non-straight comparison' do - it 'compares without fetching from another repo' do - expect(source_repo).not_to receive(:fetch_source_branch!) - - expect_compare(result, from: source_commit, to: target_commit) - expect(result.straight).to eq(false) - end - end - - context 'a straight comparison' do - let(:straight) { true } - - it 'compares without fetching from another repo' do - expect(source_repo).not_to receive(:fetch_source_branch!) - - expect_compare(result, from: source_commit, to: target_commit) - expect(result.straight).to eq(true) - end - end - end - - context 'across two repositories' do - context 'target ref exists in source repo' do - it 'compares without fetching from another repo' do - expect(source_repo).not_to receive(:fetch_source_branch!) - expect(source_repo).not_to receive(:delete_refs) - - expect_compare(result, from: source_commit, to: target_commit) - end - end - - context 'target ref does not exist in source repo' do - it 'compares in the source repo by fetching from the target to a temporary ref' do - new_commit_id = create_commit(target_project.owner, target_repo, target_branch) - new_commit = target_repo.commit(new_commit_id) - - # This is how the temporary ref is generated - expect(SecureRandom).to receive(:hex).at_least(:once).and_return('foo') - - expect(source_repo) - .to receive(:fetch_source_branch!) - .with(target_repo, new_commit_id, 'refs/tmp/foo') - .and_call_original - - expect(source_repo).to receive(:delete_refs).with('refs/tmp/foo').and_call_original - - expect_compare(result, from: source_commit, to: new_commit) - end - end - - context 'source ref does not exist in source repo' do - let(:source_branch) { 'does-not-exist' } - - it 'returns an empty comparison' do - expect(source_repo).not_to receive(:fetch_source_branch!) - expect(source_repo).not_to receive(:delete_refs) - - expect(result).to be_a(::Gitlab::Git::Compare) - expect(result.commits.size).to eq(0) - end - end - - context 'target ref does not exist in target repo' do - let(:target_branch) { 'does-not-exist' } - - it 'returns nil' do - expect(source_repo).not_to receive(:fetch_source_branch!) - expect(source_repo).not_to receive(:delete_refs) - - is_expected.to be_nil - end - end - end - end - - def expect_compare(of, from:, to:) - expect(of).to be_a(::Gitlab::Git::Compare) - expect(from).to be_a(::Gitlab::Git::Commit) - expect(to).to be_a(::Gitlab::Git::Commit) - - expect(of.commits).not_to be_empty - expect(of.head).to eq(from) - expect(of.base).to eq(to) - end - - def create_commit(user, repo, branch) - action = { action: :create, file_path: '/FILE', content: 'content' } - - result = repo.commit_files(user, branch_name: branch, message: 'Commit', actions: [action]) - - result.newrev - end -end diff --git a/spec/lib/gitlab/git/cross_repo_spec.rb b/spec/lib/gitlab/git/cross_repo_spec.rb new file mode 100644 index 00000000000..09a28c144a4 --- /dev/null +++ b/spec/lib/gitlab/git/cross_repo_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Git::CrossRepo do + let_it_be(:source_project) { create(:project, :repository) } + let_it_be(:target_project) { create(:project, :repository) } + + let(:source_repo) { source_project.repository.raw_repository } + let(:target_repo) { target_project.repository.raw_repository } + + let(:source_branch) { 'feature' } + let(:target_branch) { target_repo.root_ref } + + let(:source_commit) { source_repo.commit(source_branch) } + let(:target_commit) { source_repo.commit(target_branch) } + + def execute(&block) + described_class.new(source_repo, target_repo).execute(target_branch, &block) + end + + describe '#execute' do + context 'when executed within a single repository' do + let(:target_project) { source_project } + + it 'does not fetch from another repo' do + expect(source_repo).not_to receive(:fetch_source_branch!) + + expect { |block| execute(&block) }.to yield_with_args(target_branch) + end + end + + context 'when executed across two repositories' do + context 'and target ref exists in source repo' do + it 'does not fetch from another repo' do + expect(source_repo).not_to receive(:fetch_source_branch!) + expect(source_repo).not_to receive(:delete_refs) + + expect { |block| execute(&block) }.to yield_with_args(target_commit.id) + end + end + + context 'and target ref does not exist in source repo' do + let_it_be(:target_project) { create(:project, :repository) } + + it 'fetches from the target to a temporary ref' do + new_commit_id = create_commit(target_project.owner, target_repo, target_branch) + + # This is how the temporary ref is generated + expect(SecureRandom).to receive(:hex).at_least(:once).and_return('foo') + + expect(source_repo) + .to receive(:fetch_source_branch!) + .with(target_repo, new_commit_id, 'refs/tmp/foo') + .and_call_original + + expect(source_repo).to receive(:delete_refs).with('refs/tmp/foo').and_call_original + + expect { |block| execute(&block) }.to yield_with_args(new_commit_id) + end + end + + context 'and target ref does not exist in target repo' do + let(:target_branch) { 'does-not-exist' } + + it 'returns nil' do + expect(source_repo).not_to receive(:fetch_source_branch!) + expect(source_repo).not_to receive(:delete_refs) + + expect { |block| execute(&block) }.not_to yield_control + end + end + end + end + + def create_commit(user, repo, branch) + action = { action: :create, file_path: '/FILE', content: 'content' } + + result = repo.commit_files(user, branch_name: branch, message: 'Commit', actions: [action]) + + result.newrev + end +end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 5e27979cbf3..1984c1157fe 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -2211,15 +2211,49 @@ RSpec.describe Gitlab::Git::Repository do end describe '#compare_source_branch' do - it 'delegates to Gitlab::Git::CrossRepoComparer' do - expect_next_instance_of(::Gitlab::Git::CrossRepoComparer) do |instance| - expect(instance.source_repo).to eq(:source_repository) - expect(instance.target_repo).to eq(repository) + it 'compares two branches cross repo' do + mutable_repository.commit_files( + user, + branch_name: mutable_repository.root_ref, message: 'Committing something', + actions: [{ action: :create, file_path: 'encoding/CHANGELOG', content: 'New file' }] + ) + + repository.commit_files( + user, + branch_name: repository.root_ref, message: 'Commit to root ref', + actions: [{ action: :create, file_path: 'encoding/CHANGELOG', content: 'One more' }] + ) + + [ + [repository, mutable_repository, true], + [repository, mutable_repository, false], + [mutable_repository, repository, true], + [mutable_repository, repository, false] + ].each do |source_repo, target_repo, straight| + raw_compare = target_repo.compare_source_branch( + target_repo.root_ref, source_repo, source_repo.root_ref, straight: straight) + + expect(raw_compare).to be_a(::Gitlab::Git::Compare) - expect(instance).to receive(:compare).with('feature', 'master', straight: :straight) + expect(raw_compare.commits).to eq([source_repo.commit]) + expect(raw_compare.head).to eq(source_repo.commit) + expect(raw_compare.base).to eq(target_repo.commit) + expect(raw_compare.straight).to eq(straight) end + end - repository.compare_source_branch('master', :source_repository, 'feature', straight: :straight) + context 'source ref does not exist in source repo' do + it 'returns an empty comparison' do + expect_next_instance_of(::Gitlab::Git::CrossRepo) do |instance| + expect(instance).not_to receive(:fetch_source_branch!) + end + + raw_compare = repository.compare_source_branch( + repository.root_ref, mutable_repository, 'does-not-exist', straight: true) + + expect(raw_compare).to be_a(::Gitlab::Git::Compare) + expect(raw_compare.commits.size).to eq(0) + end end end diff --git a/spec/lib/gitlab/puma_logging/json_formatter_spec.rb b/spec/lib/gitlab/puma_logging/json_formatter_spec.rb index 64ace09e01b..d38f54bccf1 100644 --- a/spec/lib/gitlab/puma_logging/json_formatter_spec.rb +++ b/spec/lib/gitlab/puma_logging/json_formatter_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe Gitlab::PumaLogging::JSONFormatter do it "generate json format with timestamp and pid" do - Timecop.freeze( Time.utc(2019, 12, 04, 9, 10, 11, 123456)) do - expect(subject.call('log message')).to eq "{\"timestamp\":\"2019-12-04T09:10:11.123Z\",\"pid\":#{Process.pid},\"message\":\"log message\"}" + travel_to(Time.utc(2019, 12, 04, 9, 10, 11)) do + expect(subject.call('log message')).to eq "{\"timestamp\":\"2019-12-04T09:10:11.000Z\",\"pid\":#{Process.pid},\"message\":\"log message\"}" end end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 5c9a3cc0a24..f2488229a55 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -349,6 +349,23 @@ RSpec.describe Gitlab::Workhorse do expect(subject[:GitConfigOptions]).to be_empty end end + + context 'when remote_ip is available in the application context' do + it 'includes a RemoteIP params' do + result = {} + Gitlab::ApplicationContext.with_context(remote_ip: "1.2.3.4") do + result = described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action) + end + expect(result[:RemoteIP]).to eql("1.2.3.4") + end + end + + context 'when remote_ip is not available in the application context' do + it 'does not include RemoteIP params' do + result = described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action) + expect(result).not_to have_key(:RemoteIP) + end + end end describe '.set_key_and_notify' do diff --git a/spec/lib/json_web_token/hmac_token_spec.rb b/spec/lib/json_web_token/hmac_token_spec.rb index cf7e5c54f45..016084eaf69 100644 --- a/spec/lib/json_web_token/hmac_token_spec.rb +++ b/spec/lib/json_web_token/hmac_token_spec.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true require 'json' -require 'timecop' +require 'active_support/testing/time_helpers' RSpec.describe JSONWebToken::HMACToken do + include ActiveSupport::Testing::TimeHelpers + let(:secret) { 'shh secret squirrel' } shared_examples 'a valid, non-expired token' do @@ -54,13 +56,13 @@ RSpec.describe JSONWebToken::HMACToken do end context 'that is expired' do - # Needs the ! so Timecop.freeze() is effective + # Needs the ! so freeze_time() is effective let!(:encoded_token) { described_class.new(secret).encoded } it "raises exception saying 'Signature has expired'" do # Needs to be 120 seconds, because the default expiry is 60 seconds # with an additional 60 second leeway. - Timecop.freeze(Time.now + 120) do + travel_to(Time.now + 120) do expect { decoded_token }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') end end @@ -77,19 +79,19 @@ RSpec.describe JSONWebToken::HMACToken do context 'that has expired' do let(:expire_time) { 0 } + around do |example| + travel_to(Time.now + 1) { example.run } + end + context 'with the default leeway' do - Timecop.freeze(Time.now + 1) do - it_behaves_like 'a valid, non-expired token' - end + it_behaves_like 'a valid, non-expired token' end context 'with a leeway of 0 seconds' do let(:leeway) { 0 } it "raises exception saying 'Signature has expired'" do - Timecop.freeze(Time.now + 1) do - expect { decoded_token }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') - end + expect { decoded_token }.to raise_error(JWT::ExpiredSignature, 'Signature has expired') end end end diff --git a/spec/lib/peek/views/active_record_spec.rb b/spec/lib/peek/views/active_record_spec.rb index 7bc15f40065..fc768bdcb82 100644 --- a/spec/lib/peek/views/active_record_spec.rb +++ b/spec/lib/peek/views/active_record_spec.rb @@ -61,7 +61,7 @@ RSpec.describe Peek::Views::ActiveRecord, :request_store do end it 'includes db role data and db_config_name name' do - Timecop.freeze(2021, 2, 23, 10, 0) do + travel_to(Time.utc(2021, 2, 23, 10, 0)) do ActiveSupport::Notifications.publish('sql.active_record', Time.current, Time.current + 1.second, '1', event_1) ActiveSupport::Notifications.publish('sql.active_record', Time.current, Time.current + 2.seconds, '2', event_2) ActiveSupport::Notifications.publish('sql.active_record', Time.current, Time.current + 3.seconds, '3', event_3) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e76cd22d342..8cccc9ad83e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3010,44 +3010,6 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#uses_external_project_ci_config?' do - subject(:uses_external_project_ci_config) { project.uses_external_project_ci_config? } - - let(:project) { build(:project) } - - context 'when ci_config_path is configured with external project' do - before do - project.ci_config_path = '.gitlab-ci.yml@hello/world' - end - - it { is_expected.to eq(true) } - end - - context 'when ci_config_path is nil' do - before do - project.ci_config_path = nil - end - - it { is_expected.to eq(false) } - end - - context 'when ci_config_path is configured with a file in the project' do - before do - project.ci_config_path = 'hello/world/gitlab-ci.yml' - end - - it { is_expected.to eq(false) } - end - - context 'when ci_config_path is configured with remote file' do - before do - project.ci_config_path = 'https://example.org/file.yml' - end - - it { is_expected.to eq(false) } - end - end - describe '#latest_successful_build_for_ref' do let_it_be(:project) { create(:project, :repository) } let_it_be(:pipeline) { create_pipeline(project) } @@ -7518,15 +7480,6 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#ci_config_external_project' do - subject(:ci_config_external_project) { project.ci_config_external_project } - - let(:other_project) { create(:project) } - let(:project) { build(:project, ci_config_path: ".gitlab-ci.yml@#{other_project.full_path}") } - - it { is_expected.to eq(other_project) } - end - describe '#enabled_group_deploy_keys' do let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 3a9b2d02af5..4eb03204de3 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -61,6 +61,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do expect(json_response['inactive_projects_min_size_mb']).to eq(0) expect(json_response['inactive_projects_send_warning_email_after_months']).to eq(1) expect(json_response['can_create_group']).to eq(true) + expect(json_response['jira_connect_application_key']).to eq(nil) + expect(json_response['jira_connect_proxy_url']).to eq(nil) end end @@ -158,7 +160,9 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do inactive_projects_delete_after_months: 24, inactive_projects_min_size_mb: 10, inactive_projects_send_warning_email_after_months: 12, - can_create_group: false + can_create_group: false, + jira_connect_application_key: '123', + jira_connect_proxy_url: 'http://example.com' } expect(response).to have_gitlab_http_status(:ok) @@ -220,6 +224,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do expect(json_response['inactive_projects_min_size_mb']).to eq(10) expect(json_response['inactive_projects_send_warning_email_after_months']).to eq(12) expect(json_response['can_create_group']).to eq(false) + expect(json_response['jira_connect_application_key']).to eq('123') + expect(json_response['jira_connect_proxy_url']).to eq('http://example.com') end end diff --git a/spec/requests/api/usage_data_queries_spec.rb b/spec/requests/api/usage_data_queries_spec.rb index 6ce03954246..c2fb7d0c72a 100644 --- a/spec/requests/api/usage_data_queries_spec.rb +++ b/spec/requests/api/usage_data_queries_spec.rb @@ -80,7 +80,7 @@ RSpec.describe API::UsageDataQueries do end it 'matches the generated query' do - Timecop.freeze(2021, 1, 1) do + travel_to(Time.utc(2021, 1, 1)) do get api(endpoint, admin) end diff --git a/spec/serializers/entity_date_helper_spec.rb b/spec/serializers/entity_date_helper_spec.rb index a8c338675e2..5a4571339b3 100644 --- a/spec/serializers/entity_date_helper_spec.rb +++ b/spec/serializers/entity_date_helper_spec.rb @@ -48,7 +48,7 @@ RSpec.describe EntityDateHelper do describe '#remaining_days_in_words' do around do |example| - Timecop.freeze(Time.utc(2017, 3, 17)) { example.run } + travel_to(Time.utc(2017, 3, 17)) { example.run } end context 'when less than 31 days remaining' do @@ -75,7 +75,9 @@ RSpec.describe EntityDateHelper do end it 'returns 1 day remaining when queried mid-day' do - Timecop.freeze(Time.utc(2017, 3, 17, 13, 10)) do + travel_back + + travel_to(Time.utc(2017, 3, 17, 13, 10)) do expect(milestone_remaining).to eq("<strong>1</strong> day remaining") end end diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb index 5fdefb2b306..b866293393b 100644 --- a/spec/services/ci/create_pipeline_service/rules_spec.rb +++ b/spec/services/ci/create_pipeline_service/rules_spec.rb @@ -912,7 +912,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes context 'when outside freeze period' do it 'creates two jobs' do - Timecop.freeze(2020, 4, 10, 22, 59) do + travel_to(Time.utc(2020, 4, 10, 22, 59)) do expect(pipeline).to be_persisted expect(build_names).to contain_exactly('test-job', 'deploy-job') end @@ -921,7 +921,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes context 'when inside freeze period' do it 'creates one job' do - Timecop.freeze(2020, 4, 10, 23, 1) do + travel_to(Time.utc(2020, 4, 10, 23, 1)) do expect(pipeline).to be_persisted expect(build_names).to contain_exactly('test-job') end @@ -946,7 +946,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes context 'when outside freeze period' do it 'creates two jobs' do - Timecop.freeze(2020, 4, 10, 22, 59) do + travel_to(Time.utc(2020, 4, 10, 22, 59)) do expect(pipeline).to be_persisted expect(build_names).to contain_exactly('deploy-job') end @@ -955,7 +955,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes context 'when inside freeze period' do it 'does not create the pipeline', :aggregate_failures do - Timecop.freeze(2020, 4, 10, 23, 1) do + travel_to(Time.utc(2020, 4, 10, 23, 1)) do expect(response).to be_error expect(pipeline).not_to be_persisted end diff --git a/spec/support/banzai/filter_timeout_shared_examples.rb b/spec/support/banzai/filter_timeout_shared_examples.rb new file mode 100644 index 00000000000..1f2ebe6fef6 --- /dev/null +++ b/spec/support/banzai/filter_timeout_shared_examples.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# This shared_example requires the following variables: +# - text: The text to be run through the filter +# +# Usage: +# +# it_behaves_like 'filter timeout' do +# let(:text) { 'some text' } +# end +RSpec.shared_examples 'filter timeout' do + context 'when rendering takes too long' do + let_it_be(:project) { create(:project) } + let_it_be(:context) { { project: project } } + + it 'times out' do + stub_const("Banzai::Filter::TimeoutHtmlPipelineFilter::RENDER_TIMEOUT", 0.1) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:call_with_timeout) do + sleep(0.2) + text + end + end + + expect(Gitlab::RenderTimeout).to receive(:timeout).and_call_original + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( + instance_of(Timeout::Error), + project_id: context[:project].id, + class_name: described_class.name.demodulize + ) + + result = filter(text) + + expect(result.to_html).to eq text + end + end +end diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb index f866220b919..816caf5f775 100644 --- a/spec/support/cycle_analytics_helpers/test_generation.rb +++ b/spec/support/cycle_analytics_helpers/test_generation.rb @@ -42,17 +42,17 @@ module CycleAnalyticsHelpers end_time = start_time + rand(1..5).days start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } + travel_to(start_time) { condition_fn[self, data] } end # Run `before_end_fn` at the midpoint between `start_time` and `end_time` - Timecop.freeze(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn + travel_to(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn end_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(end_time) { condition_fn[self, data] } + travel_to(end_time) { condition_fn[self, data] } end - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + travel_to(end_time + 1.day) { post_fn[self, data] } if post_fn end_time - start_time end @@ -74,14 +74,14 @@ module CycleAnalyticsHelpers end_time = rand(1..10).days.from_now start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } + travel_to(start_time) { condition_fn[self, data] } end end_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(end_time) { condition_fn[self, data] } + travel_to(end_time) { condition_fn[self, data] } end - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + travel_to(end_time + 1.day) { post_fn[self, data] } if post_fn # Turn off the stub before checking assertions allow(self).to receive(:project).and_call_original @@ -97,17 +97,17 @@ module CycleAnalyticsHelpers end_time = start_time + rand(1..5).days # Run `before_end_fn` at the midpoint between `start_time` and `end_time` - Timecop.freeze(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn + travel_to(start_time + (end_time - start_time) / 2) { before_end_fn[self, data] } if before_end_fn end_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } + travel_to(start_time) { condition_fn[self, data] } end start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(end_time) { condition_fn[self, data] } + travel_to(end_time) { condition_fn[self, data] } end - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + travel_to(end_time + 1.day) { post_fn[self, data] } if post_fn expect(subject[phase].project_median).to be_nil end @@ -122,10 +122,10 @@ module CycleAnalyticsHelpers end_time = rand(1..10).days.from_now end_time_conditions.each_with_index do |(_condition_name, condition_fn), index| - Timecop.freeze(end_time + index.days) { condition_fn[self, data] } + travel_to(end_time + index.days) { condition_fn[self, data] } end - Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn + travel_to(end_time + 1.day) { post_fn[self, data] } if post_fn expect(subject[phase].project_median).to be_nil end @@ -139,7 +139,7 @@ module CycleAnalyticsHelpers start_time = Time.now start_time_conditions.each do |condition_name, condition_fn| - Timecop.freeze(start_time) { condition_fn[self, data] } + travel_to(start_time) { condition_fn[self, data] } end post_fn[self, data] if post_fn diff --git a/spec/support/helpers/features/runners_helpers.rb b/spec/support/helpers/features/runners_helpers.rb index 63fc628358c..c5d26108953 100644 --- a/spec/support/helpers/features/runners_helpers.rb +++ b/spec/support/helpers/features/runners_helpers.rb @@ -50,7 +50,7 @@ module Spec page.within(search_bar_selector) do click_on filter - # For OPERATOR_IS_ONLY, clicking the filter + # For OPERATORS_IS, clicking the filter # immediately preselects "=" operator page.find('input').send_keys(value) diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 32e6e8d50bd..40eb46878ad 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -3,12 +3,14 @@ require 'action_dispatch/testing/test_request' require 'fileutils' require 'graphlyte' +require 'active_support/testing/time_helpers' require_relative '../../../lib/gitlab/popen' module JavaScriptFixturesHelpers extend ActiveSupport::Concern include Gitlab::Popen + include ActiveSupport::Testing::TimeHelpers extend self @@ -22,7 +24,7 @@ module JavaScriptFixturesHelpers # pick an arbitrary date from the past, so tests are not time dependent # Also see spec/frontend/__helpers__/fake_date/jest.js - Timecop.freeze(Time.utc(2015, 7, 3, 10)) { example.run } + travel_to(Time.utc(2015, 7, 3, 10)) { example.run } raise NoMethodError.new('You need to set `response` for the fixture generator! This will automatically happen with `type: :controller` or `type: :request`.', 'response') unless respond_to?(:response) diff --git a/spec/support/shared_contexts/rack_attack_shared_context.rb b/spec/support/shared_contexts/rack_attack_shared_context.rb index e7b2ee76c3c..12625ead72b 100644 --- a/spec/support/shared_contexts/rack_attack_shared_context.rb +++ b/spec/support/shared_contexts/rack_attack_shared_context.rb @@ -6,7 +6,7 @@ RSpec.shared_context 'rack attack cache store' do Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # Make time-dependent tests deterministic - Timecop.freeze { example.run } + freeze_time { example.run } Rack::Attack.cache.store = Rails.cache end diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index 11759b6671f..82ed6eb4c95 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -68,6 +68,7 @@ RSpec.shared_examples 'rate-limited token requests' do # Set low limits settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds + travel_back end after do @@ -220,6 +221,7 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do # Set low limits settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds + travel_back end after do @@ -436,6 +438,7 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do # Set low limits settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds + travel_back end context 'when the throttle is enabled' do diff --git a/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb b/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb index 11343f69d6f..491ea64cff1 100644 --- a/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb +++ b/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb @@ -10,23 +10,24 @@ RSpec.describe Metrics::Dashboard::PruneOldAnnotationsWorker do describe '#perform' do it 'removes all annotations older than cut off', :aggregate_failures do - Timecop.freeze(now) do + travel_to(now) do described_class.new.perform expect(Metrics::Dashboard::Annotation.all).to match_array([one_day_old_annotation, two_weeks_old_annotation]) # is idempotent in the scope of 24h expect { described_class.new.perform }.not_to change { Metrics::Dashboard::Annotation.all.to_a } - travel_to(24.hours.from_now) do - described_class.new.perform - expect(Metrics::Dashboard::Annotation.all).to match_array([one_day_old_annotation]) - end + end + + travel_to(now + 24.hours) do + described_class.new.perform + expect(Metrics::Dashboard::Annotation.all).to match_array([one_day_old_annotation]) end end context 'batch to be deleted is bigger than upper limit' do it 'schedules second job to clear remaining records' do - Timecop.freeze(now) do + travel_to(now) do create(:metrics_dashboard_annotation, starting_at: 1.month.ago) stub_const("#{described_class}::DELETE_LIMIT", 1) |