diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-14 15:09:44 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-14 15:09:44 +0000 |
commit | 874ead9c3a50de4c4ca4551eaf5b7eb976d26b50 (patch) | |
tree | 637ee9f2da5e251bc08ebf3e972209d51966bf7c /spec | |
parent | 2e4c4055181eec9186458dd5dd3219c937032ec7 (diff) | |
download | gitlab-ce-874ead9c3a50de4c4ca4551eaf5b7eb976d26b50.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
28 files changed, 732 insertions, 367 deletions
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 74ed4a0f991..fdc8fe5f082 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1085,6 +1085,48 @@ describe Projects::IssuesController do expect { subject }.to change(SentryIssue, :count) end end + + context 'when the endpoint receives requests above the limit' do + before do + stub_application_setting(issues_create_limit: 5) + end + + it 'prevents from creating more issues', :request_store do + 5.times { post_new_issue } + + expect { post_new_issue } + .to change { Gitlab::GitalyClient.get_request_count }.by(1) # creates 1 projects and 0 issues + + post_new_issue + expect(response.body).to eq(_('This endpoint has been requested too many times. Try again later.')) + expect(response).to have_gitlab_http_status(:too_many_requests) + end + + it 'logs the event on auth.log' do + attributes = { + message: 'Application_Rate_Limiter_Request', + env: :issues_create_request_limit, + remote_ip: '0.0.0.0', + request_method: 'POST', + path: "/#{project.full_path}/-/issues", + user_id: user.id, + username: user.username + } + + expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once + + project.add_developer(user) + sign_in(user) + + 6.times do + post :create, params: { + namespace_id: project.namespace.to_param, + project_id: project, + issue: { title: 'Title', description: 'Description' } + } + end + end + end end describe 'POST #mark_as_spam' do diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb index 82383cfa2b0..a259c5142fc 100644 --- a/spec/factories/ci/job_artifacts.rb +++ b/spec/factories/ci/job_artifacts.rb @@ -13,7 +13,7 @@ FactoryBot.define do end trait :remote_store do - file_store { JobArtifactUploader::Store::REMOTE} + file_store { JobArtifactUploader::Store::REMOTE } end after :build do |artifact| diff --git a/spec/factories/diff_note_positions.rb b/spec/factories/diff_note_positions.rb new file mode 100644 index 00000000000..6e95e306d50 --- /dev/null +++ b/spec/factories/diff_note_positions.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :diff_note_position do + association :note, factory: :diff_note_on_merge_request + line_code { note.line_code } + position { note.position } + diff_type { :head } + end +end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index d7f12411a93..cee9b6d50ba 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -399,10 +399,12 @@ describe 'Environments page', :js do describe 'environments folders' do before do - create(:environment, project: project, + create(:environment, :will_auto_stop, + project: project, name: 'staging/review-1', state: :available) - create(:environment, project: project, + create(:environment, :will_auto_stop, + project: project, name: 'staging/review-2', state: :available) end @@ -420,6 +422,14 @@ describe 'Environments page', :js do expect(page).to have_content 'review-1' expect(page).to have_content 'review-2' + within('.ci-table') do + within('.gl-responsive-table-row:nth-child(3)') do + expect(find('.js-auto-stop').text).not_to be_empty + end + within('.gl-responsive-table-row:nth-child(4)') do + expect(find('.js-auto-stop').text).not_to be_empty + end + end end end diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index 3aad4c87237..870e47edde0 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -50,6 +50,7 @@ describe('Time series component', () => { propsData: { graphData: { ...graphData, type }, deploymentData: store.state.monitoringDashboard.deploymentData, + annotations: store.state.monitoringDashboard.annotations, projectPath: `${mockHost}${mockProjectDir}`, }, store, diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index d6faec29b65..c34a5afceb0 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -16,6 +16,7 @@ import { fetchDeploymentsData, fetchEnvironmentsData, fetchDashboardData, + fetchAnnotations, fetchPrometheusMetric, setInitialState, filterEnvironments, @@ -24,10 +25,12 @@ import { } from '~/monitoring/stores/actions'; import { gqClient, parseEnvironmentsResponse } from '~/monitoring/stores/utils'; import getEnvironments from '~/monitoring/queries/getEnvironments.query.graphql'; +import getAnnotations from '~/monitoring/queries/getAnnotations.query.graphql'; import storeState from '~/monitoring/stores/state'; import { deploymentData, environmentData, + annotationsData, metricsDashboardResponse, metricsDashboardViewModel, dashboardGitResponse, @@ -120,17 +123,15 @@ describe('Monitoring store actions', () => { }); it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => { - jest.spyOn(gqClient, 'mutate').mockReturnValue( - Promise.resolve({ - data: { - project: { - data: { - environments: [], - }, + jest.spyOn(gqClient, 'mutate').mockReturnValue({ + data: { + project: { + data: { + environments: [], }, }, - }), - ); + }, + }); return testAction( filterEnvironments, @@ -180,17 +181,15 @@ describe('Monitoring store actions', () => { }); it('dispatches receiveEnvironmentsDataSuccess on success', () => { - jest.spyOn(gqClient, 'mutate').mockReturnValue( - Promise.resolve({ - data: { - project: { - data: { - environments: environmentData, - }, + jest.spyOn(gqClient, 'mutate').mockResolvedValue({ + data: { + project: { + data: { + environments: environmentData, }, }, - }), - ); + }, + }); return testAction( fetchEnvironmentsData, @@ -208,7 +207,7 @@ describe('Monitoring store actions', () => { }); it('dispatches receiveEnvironmentsDataFailure on error', () => { - jest.spyOn(gqClient, 'mutate').mockReturnValue(Promise.reject()); + jest.spyOn(gqClient, 'mutate').mockRejectedValue({}); return testAction( fetchEnvironmentsData, @@ -220,6 +219,80 @@ describe('Monitoring store actions', () => { }); }); + describe('fetchAnnotations', () => { + const { state } = store; + state.projectPath = 'gitlab-org/gitlab-test'; + state.currentEnvironmentName = 'production'; + state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml'; + + afterEach(() => { + resetStore(store); + }); + + it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => { + const mockMutate = jest.spyOn(gqClient, 'mutate'); + const mutationVariables = { + mutation: getAnnotations, + variables: { + projectPath: state.projectPath, + environmentName: state.currentEnvironmentName, + dashboardId: state.currentDashboard, + }, + }; + + mockMutate.mockResolvedValue({ + data: { + project: { + environment: { + metricDashboard: { + annotations: annotationsData, + }, + }, + }, + }, + }); + + return testAction( + fetchAnnotations, + null, + state, + [], + [ + { type: 'requestAnnotations' }, + { type: 'receiveAnnotationsSuccess', payload: annotationsData }, + ], + () => { + expect(mockMutate).toHaveBeenCalledWith(mutationVariables); + }, + ); + }); + + it('dispatches receiveAnnotationsFailure if the annotations API call fails', () => { + const mockMutate = jest.spyOn(gqClient, 'mutate'); + const mutationVariables = { + mutation: getAnnotations, + variables: { + projectPath: state.projectPath, + environmentName: state.currentEnvironmentName, + dashboardId: state.currentDashboard, + }, + }; + + mockMutate.mockRejectedValue({}); + + return testAction( + fetchAnnotations, + null, + state, + [], + [{ type: 'requestAnnotations' }, { type: 'receiveAnnotationsFailure' }], + () => { + expect(mockMutate).toHaveBeenCalledWith(mutationVariables); + }, + ); + }); + }); + describe('Set initial state', () => { let mockedState; beforeEach(() => { diff --git a/spec/frontend/smart_interval_spec.js b/spec/frontend/smart_interval_spec.js new file mode 100644 index 00000000000..b32ac99e4e4 --- /dev/null +++ b/spec/frontend/smart_interval_spec.js @@ -0,0 +1,197 @@ +import $ from 'jquery'; +import { assignIn } from 'lodash'; +import waitForPromises from 'helpers/wait_for_promises'; +import SmartInterval from '~/smart_interval'; + +jest.useFakeTimers(); + +let interval; + +describe('SmartInterval', () => { + const DEFAULT_MAX_INTERVAL = 100; + const DEFAULT_STARTING_INTERVAL = 5; + const DEFAULT_INCREMENT_FACTOR = 2; + + function createDefaultSmartInterval(config) { + const defaultParams = { + callback: () => Promise.resolve(), + startingInterval: DEFAULT_STARTING_INTERVAL, + maxInterval: DEFAULT_MAX_INTERVAL, + incrementByFactorOf: DEFAULT_INCREMENT_FACTOR, + lazyStart: false, + immediateExecution: false, + hiddenInterval: null, + }; + + if (config) { + assignIn(defaultParams, config); + } + + return new SmartInterval(defaultParams); + } + + afterEach(() => { + interval.destroy(); + }); + + describe('Increment Interval', () => { + it('should increment the interval delay', () => { + interval = createDefaultSmartInterval(); + + jest.runOnlyPendingTimers(); + + return waitForPromises().then(() => { + const intervalConfig = interval.cfg; + const iterationCount = 4; + const maxIntervalAfterIterations = + intervalConfig.startingInterval * intervalConfig.incrementByFactorOf ** iterationCount; + const currentInterval = interval.getCurrentInterval(); + + // Provide some flexibility for performance of testing environment + expect(currentInterval).toBeGreaterThan(intervalConfig.startingInterval); + expect(currentInterval).toBeLessThanOrEqual(maxIntervalAfterIterations); + }); + }); + + it('should not increment past maxInterval', () => { + interval = createDefaultSmartInterval({ maxInterval: DEFAULT_STARTING_INTERVAL }); + + jest.runOnlyPendingTimers(); + + return waitForPromises().then(() => { + const currentInterval = interval.getCurrentInterval(); + + expect(currentInterval).toBe(interval.cfg.maxInterval); + }); + }); + + it('does not increment while waiting for callback', () => { + interval = createDefaultSmartInterval({ + callback: () => new Promise($.noop), + }); + + jest.runOnlyPendingTimers(); + + return waitForPromises().then(() => { + const oneInterval = interval.cfg.startingInterval * DEFAULT_INCREMENT_FACTOR; + + expect(interval.getCurrentInterval()).toEqual(oneInterval); + }); + }); + }); + + describe('Public methods', () => { + beforeEach(() => { + interval = createDefaultSmartInterval(); + }); + + it('should cancel an interval', () => { + jest.runOnlyPendingTimers(); + + interval.cancel(); + + return waitForPromises().then(() => { + const { intervalId } = interval.state; + const currentInterval = interval.getCurrentInterval(); + const intervalLowerLimit = interval.cfg.startingInterval; + + expect(intervalId).toBeUndefined(); + expect(currentInterval).toBe(intervalLowerLimit); + }); + }); + + it('should resume an interval', () => { + jest.runOnlyPendingTimers(); + + interval.cancel(); + + interval.resume(); + + return waitForPromises().then(() => { + const { intervalId } = interval.state; + + expect(intervalId).toBeTruthy(); + }); + }); + }); + + describe('DOM Events', () => { + beforeEach(() => { + // This ensures DOM and DOM events are initialized for these specs. + setFixtures('<div></div>'); + + interval = createDefaultSmartInterval(); + }); + + it('should pause when page is not visible', () => { + jest.runOnlyPendingTimers(); + + return waitForPromises().then(() => { + expect(interval.state.intervalId).toBeTruthy(); + + // simulates triggering of visibilitychange event + interval.onVisibilityChange({ target: { visibilityState: 'hidden' } }); + + expect(interval.state.intervalId).toBeUndefined(); + }); + }); + + it('should change to the hidden interval when page is not visible', () => { + interval.destroy(); + + const HIDDEN_INTERVAL = 1500; + interval = createDefaultSmartInterval({ hiddenInterval: HIDDEN_INTERVAL }); + + jest.runOnlyPendingTimers(); + + return waitForPromises().then(() => { + expect(interval.state.intervalId).toBeTruthy(); + expect( + interval.getCurrentInterval() >= DEFAULT_STARTING_INTERVAL && + interval.getCurrentInterval() <= DEFAULT_MAX_INTERVAL, + ).toBeTruthy(); + + // simulates triggering of visibilitychange event + interval.onVisibilityChange({ target: { visibilityState: 'hidden' } }); + + expect(interval.state.intervalId).toBeTruthy(); + expect(interval.getCurrentInterval()).toBe(HIDDEN_INTERVAL); + }); + }); + + it('should resume when page is becomes visible at the previous interval', () => { + jest.runOnlyPendingTimers(); + + return waitForPromises().then(() => { + expect(interval.state.intervalId).toBeTruthy(); + + // simulates triggering of visibilitychange event + interval.onVisibilityChange({ target: { visibilityState: 'hidden' } }); + + expect(interval.state.intervalId).toBeUndefined(); + + // simulates triggering of visibilitychange event + interval.onVisibilityChange({ target: { visibilityState: 'visible' } }); + + expect(interval.state.intervalId).toBeTruthy(); + }); + }); + + it('should cancel on page unload', () => { + jest.runOnlyPendingTimers(); + + return waitForPromises().then(() => { + $(document).triggerHandler('beforeunload'); + + expect(interval.state.intervalId).toBeUndefined(); + expect(interval.getCurrentInterval()).toBe(interval.cfg.startingInterval); + }); + }); + + it('should execute callback before first interval', () => { + interval = createDefaultSmartInterval({ immediateExecution: true }); + + expect(interval.cfg.immediateExecution).toBeFalsy(); + }); + }); +}); diff --git a/spec/frontend/static_site_editor/components/static_site_editor_spec.js b/spec/frontend/static_site_editor/components/static_site_editor_spec.js index a40f8edbeb2..2c4fa0e061a 100644 --- a/spec/frontend/static_site_editor/components/static_site_editor_spec.js +++ b/spec/frontend/static_site_editor/components/static_site_editor_spec.js @@ -30,7 +30,6 @@ describe('StaticSiteEditor', () => { store = new Vuex.Store({ state: createState(initialState), getters: { - isContentLoaded: () => false, contentChanged: () => false, ...getters, }, @@ -43,9 +42,11 @@ describe('StaticSiteEditor', () => { }; const buildContentLoadedStore = ({ initialState, getters } = {}) => { buildStore({ - initialState, + initialState: { + isContentLoaded: true, + ...initialState, + }, getters: { - isContentLoaded: () => true, ...getters, }, }); @@ -85,7 +86,7 @@ describe('StaticSiteEditor', () => { const content = 'edit area content'; beforeEach(() => { - buildStore({ initialState: { content }, getters: { isContentLoaded: () => true } }); + buildContentLoadedStore({ initialState: { content } }); buildWrapper(); }); diff --git a/spec/frontend/static_site_editor/store/getters_spec.js b/spec/frontend/static_site_editor/store/getters_spec.js index 1b482db9366..5793e344784 100644 --- a/spec/frontend/static_site_editor/store/getters_spec.js +++ b/spec/frontend/static_site_editor/store/getters_spec.js @@ -1,18 +1,8 @@ import createState from '~/static_site_editor/store/state'; -import { isContentLoaded, contentChanged } from '~/static_site_editor/store/getters'; +import { contentChanged } from '~/static_site_editor/store/getters'; import { sourceContent as content } from '../mock_data'; describe('Static Site Editor Store getters', () => { - describe('isContentLoaded', () => { - it('returns true when originalContent is not empty', () => { - expect(isContentLoaded(createState({ originalContent: content }))).toBe(true); - }); - - it('returns false when originalContent is empty', () => { - expect(isContentLoaded(createState({ originalContent: '' }))).toBe(false); - }); - }); - describe('contentChanged', () => { it('returns true when content and originalContent are different', () => { const state = createState({ content, originalContent: 'something else' }); diff --git a/spec/frontend/static_site_editor/store/mutations_spec.js b/spec/frontend/static_site_editor/store/mutations_spec.js index 1fd687eed4a..0b213c11a04 100644 --- a/spec/frontend/static_site_editor/store/mutations_spec.js +++ b/spec/frontend/static_site_editor/store/mutations_spec.js @@ -19,6 +19,7 @@ describe('Static Site Editor Store mutations', () => { mutation | stateProperty | payload | expectedValue ${types.LOAD_CONTENT} | ${'isLoadingContent'} | ${undefined} | ${true} ${types.RECEIVE_CONTENT_SUCCESS} | ${'isLoadingContent'} | ${contentLoadedPayload} | ${false} + ${types.RECEIVE_CONTENT_SUCCESS} | ${'isContentLoaded'} | ${contentLoadedPayload} | ${true} ${types.RECEIVE_CONTENT_SUCCESS} | ${'title'} | ${contentLoadedPayload} | ${title} ${types.RECEIVE_CONTENT_SUCCESS} | ${'content'} | ${contentLoadedPayload} | ${content} ${types.RECEIVE_CONTENT_SUCCESS} | ${'originalContent'} | ${contentLoadedPayload} | ${content} diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js index ef95cb1b8f2..e022f68fdec 100644 --- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js @@ -273,25 +273,6 @@ describe('mrWidgetOptions', () => { }; }); - it('should not tell service to check status if document is not visible', () => { - Object.defineProperty(document, 'visibilityState', { - value: 'hidden', - configurable: true, - }); - vm.checkStatus(cb); - - return vm.$nextTick().then(() => { - expect(vm.service.checkStatus).not.toHaveBeenCalled(); - expect(vm.mr.setData).not.toHaveBeenCalled(); - expect(vm.handleNotification).not.toHaveBeenCalled(); - expect(isCbExecuted).toBeFalsy(); - Object.defineProperty(document, 'visibilityState', { - value: 'visible', - configurable: true, - }); - }); - }); - it('should tell service to check status if document is visible', () => { vm.checkStatus(cb); diff --git a/spec/javascripts/smart_interval_spec.js b/spec/javascripts/smart_interval_spec.js deleted file mode 100644 index 0dc9ee9d79a..00000000000 --- a/spec/javascripts/smart_interval_spec.js +++ /dev/null @@ -1,234 +0,0 @@ -import $ from 'jquery'; -import { assignIn } from 'lodash'; -import waitForPromises from 'spec/helpers/wait_for_promises'; -import SmartInterval from '~/smart_interval'; - -describe('SmartInterval', function() { - const DEFAULT_MAX_INTERVAL = 100; - const DEFAULT_STARTING_INTERVAL = 5; - const DEFAULT_SHORT_TIMEOUT = 75; - const DEFAULT_INCREMENT_FACTOR = 2; - - function createDefaultSmartInterval(config) { - const defaultParams = { - callback: () => Promise.resolve(), - startingInterval: DEFAULT_STARTING_INTERVAL, - maxInterval: DEFAULT_MAX_INTERVAL, - incrementByFactorOf: DEFAULT_INCREMENT_FACTOR, - lazyStart: false, - immediateExecution: false, - hiddenInterval: null, - }; - - if (config) { - assignIn(defaultParams, config); - } - - return new SmartInterval(defaultParams); - } - - beforeEach(() => { - jasmine.clock().install(); - }); - - afterEach(() => { - jasmine.clock().uninstall(); - }); - - describe('Increment Interval', function() { - it('should increment the interval delay', done => { - const smartInterval = createDefaultSmartInterval(); - - jasmine.clock().tick(DEFAULT_SHORT_TIMEOUT); - - waitForPromises() - .then(() => { - const intervalConfig = smartInterval.cfg; - const iterationCount = 4; - const maxIntervalAfterIterations = - intervalConfig.startingInterval * intervalConfig.incrementByFactorOf ** iterationCount; - const currentInterval = smartInterval.getCurrentInterval(); - - // Provide some flexibility for performance of testing environment - expect(currentInterval).toBeGreaterThan(intervalConfig.startingInterval); - expect(currentInterval).toBeLessThanOrEqual(maxIntervalAfterIterations); - }) - .then(done) - .catch(done.fail); - }); - - it('should not increment past maxInterval', done => { - const smartInterval = createDefaultSmartInterval({ maxInterval: DEFAULT_STARTING_INTERVAL }); - - jasmine.clock().tick(DEFAULT_STARTING_INTERVAL); - jasmine.clock().tick(DEFAULT_STARTING_INTERVAL * DEFAULT_INCREMENT_FACTOR); - - waitForPromises() - .then(() => { - const currentInterval = smartInterval.getCurrentInterval(); - - expect(currentInterval).toBe(smartInterval.cfg.maxInterval); - }) - .then(done) - .catch(done.fail); - }); - - it('does not increment while waiting for callback', done => { - const smartInterval = createDefaultSmartInterval({ - callback: () => new Promise($.noop), - }); - - jasmine.clock().tick(DEFAULT_SHORT_TIMEOUT); - - waitForPromises() - .then(() => { - const oneInterval = smartInterval.cfg.startingInterval * DEFAULT_INCREMENT_FACTOR; - - expect(smartInterval.getCurrentInterval()).toEqual(oneInterval); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('Public methods', function() { - beforeEach(function() { - this.smartInterval = createDefaultSmartInterval(); - }); - - it('should cancel an interval', function(done) { - const interval = this.smartInterval; - - jasmine.clock().tick(DEFAULT_SHORT_TIMEOUT); - - interval.cancel(); - - waitForPromises() - .then(() => { - const { intervalId } = interval.state; - const currentInterval = interval.getCurrentInterval(); - const intervalLowerLimit = interval.cfg.startingInterval; - - expect(intervalId).toBeUndefined(); - expect(currentInterval).toBe(intervalLowerLimit); - }) - .then(done) - .catch(done.fail); - }); - - it('should resume an interval', function(done) { - const interval = this.smartInterval; - - jasmine.clock().tick(DEFAULT_SHORT_TIMEOUT); - - interval.cancel(); - - interval.resume(); - - waitForPromises() - .then(() => { - const { intervalId } = interval.state; - - expect(intervalId).toBeTruthy(); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('DOM Events', function() { - beforeEach(function() { - // This ensures DOM and DOM events are initialized for these specs. - setFixtures('<div></div>'); - - this.smartInterval = createDefaultSmartInterval(); - }); - - it('should pause when page is not visible', function(done) { - const interval = this.smartInterval; - - jasmine.clock().tick(DEFAULT_SHORT_TIMEOUT); - - waitForPromises() - .then(() => { - expect(interval.state.intervalId).toBeTruthy(); - - // simulates triggering of visibilitychange event - interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); - - expect(interval.state.intervalId).toBeUndefined(); - }) - .then(done) - .catch(done.fail); - }); - - it('should change to the hidden interval when page is not visible', done => { - const HIDDEN_INTERVAL = 1500; - const interval = createDefaultSmartInterval({ hiddenInterval: HIDDEN_INTERVAL }); - - jasmine.clock().tick(DEFAULT_SHORT_TIMEOUT); - - waitForPromises() - .then(() => { - expect(interval.state.intervalId).toBeTruthy(); - expect( - interval.getCurrentInterval() >= DEFAULT_STARTING_INTERVAL && - interval.getCurrentInterval() <= DEFAULT_MAX_INTERVAL, - ).toBeTruthy(); - - // simulates triggering of visibilitychange event - interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); - - expect(interval.state.intervalId).toBeTruthy(); - expect(interval.getCurrentInterval()).toBe(HIDDEN_INTERVAL); - }) - .then(done) - .catch(done.fail); - }); - - it('should resume when page is becomes visible at the previous interval', function(done) { - const interval = this.smartInterval; - - jasmine.clock().tick(DEFAULT_SHORT_TIMEOUT); - - waitForPromises() - .then(() => { - expect(interval.state.intervalId).toBeTruthy(); - - // simulates triggering of visibilitychange event - interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); - - expect(interval.state.intervalId).toBeUndefined(); - - // simulates triggering of visibilitychange event - interval.handleVisibilityChange({ target: { visibilityState: 'visible' } }); - - expect(interval.state.intervalId).toBeTruthy(); - }) - .then(done) - .catch(done.fail); - }); - - it('should cancel on page unload', function(done) { - const interval = this.smartInterval; - - jasmine.clock().tick(DEFAULT_SHORT_TIMEOUT); - - waitForPromises() - .then(() => { - $(document).triggerHandler('beforeunload'); - - expect(interval.state.intervalId).toBeUndefined(); - expect(interval.getCurrentInterval()).toBe(interval.cfg.startingInterval); - }) - .then(done) - .catch(done.fail); - }); - - it('should execute callback before first interval', function() { - const interval = createDefaultSmartInterval({ immediateExecution: true }); - - expect(interval.cfg.immediateExecution).toBeFalsy(); - }); - }); -}); diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb index e11613b202d..6e8a8c03aad 100644 --- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb @@ -113,28 +113,22 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_r end describe 'droppable?' do - where(:idempotent, :duplicate, :feature_enabled) do - # [true, false].repeated_permutation(3) - [[true, true, true], - [true, true, false], - [true, false, true], - [true, false, false], - [false, true, true], - [false, true, false], - [false, false, true], - [false, false, false]] + where(:idempotent, :duplicate) do + # [true, false].repeated_permutation(2) + [[true, true], + [true, false], + [false, true], + [false, false]] end with_them do before do allow(AuthorizedProjectsWorker).to receive(:idempotent?).and_return(idempotent) allow(duplicate_job).to receive(:duplicate?).and_return(duplicate) - allow(Gitlab::SidekiqMiddleware::DuplicateJobs) - .to receive(:drop_duplicates?).with(queue).and_return(feature_enabled) end it 'is droppable when all conditions are met' do - if idempotent && duplicate && feature_enabled + if idempotent && duplicate expect(duplicate_job).to be_droppable else expect(duplicate_job).not_to be_droppable diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs_spec.rb deleted file mode 100644 index fa5938f470b..00000000000 --- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::SidekiqMiddleware::DuplicateJobs do - using RSpec::Parameterized::TableSyntax - - describe '.drop_duplicates?' do - where(:global_feature_enabled, :selected_queue_enabled, :queue, :expected) do - true | true | described_class::DROPPABLE_QUEUES.first | true - true | true | "other_queue" | true - true | false | described_class::DROPPABLE_QUEUES.first | true - true | false | "other_queue" | true - false | true | described_class::DROPPABLE_QUEUES.first | true - false | true | "other_queue" | false - false | false | described_class::DROPPABLE_QUEUES.first | false - false | false | "other_queue" | false - end - - with_them do - before do - stub_feature_flags(drop_duplicate_sidekiq_jobs: global_feature_enabled, - drop_duplicate_sidekiq_jobs_for_queue: selected_queue_enabled) - end - - it "allows dropping jobs when expected" do - expect(described_class.drop_duplicates?(queue)).to be(expected) - end - end - end -end diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb index 78887cef7ab..5029a17e4e5 100644 --- a/spec/mailers/emails/pages_domains_spec.rb +++ b/spec/mailers/emails/pages_domains_spec.rb @@ -23,13 +23,20 @@ describe Emails::PagesDomains do is_expected.to have_subject(email_subject) is_expected.to have_body_text(project.human_name) is_expected.to have_body_text(domain.domain) - is_expected.to have_body_text domain.url is_expected.to have_body_text project_pages_domain_url(project, domain) - is_expected.to have_body_text help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: link_anchor) end end end + shared_examples 'a pages domain verification email' do + it_behaves_like 'a pages domain email' + + it 'has the expected content' do + is_expected.to have_body_text domain.url + is_expected.to have_body_text help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: link_anchor) + end + end + shared_examples 'notification about upcoming domain removal' do context 'when domain is not scheduled for removal' do it 'asks user to remove it' do @@ -56,7 +63,7 @@ describe Emails::PagesDomains do subject { Notify.pages_domain_enabled_email(domain, user) } - it_behaves_like 'a pages domain email' + it_behaves_like 'a pages domain verification email' it { is_expected.to have_body_text 'has been enabled' } end @@ -67,7 +74,7 @@ describe Emails::PagesDomains do subject { Notify.pages_domain_disabled_email(domain, user) } - it_behaves_like 'a pages domain email' + it_behaves_like 'a pages domain verification email' it_behaves_like 'notification about upcoming domain removal' @@ -80,7 +87,7 @@ describe Emails::PagesDomains do subject { Notify.pages_domain_verification_succeeded_email(domain, user) } - it_behaves_like 'a pages domain email' + it_behaves_like 'a pages domain verification email' it { is_expected.to have_body_text 'successfully verified' } end @@ -94,10 +101,18 @@ describe Emails::PagesDomains do it_behaves_like 'a pages domain email' it_behaves_like 'notification about upcoming domain removal' + end + + describe '#pages_domain_auto_ssl_failed_email' do + let(:email_subject) { "#{project.path} | ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '#{domain.domain}'" } + + subject { Notify.pages_domain_auto_ssl_failed_email(domain, user) } + + it_behaves_like 'a pages domain email' - it 'says verification has failed and when the domain is enabled until' do - is_expected.to have_body_text 'Verification has failed' - is_expected.to have_body_text domain.enabled_until.strftime('%F %T') + it 'says that we failed to obtain certificate' do + is_expected.to have_body_text "Something went wrong while obtaining the Let's Encrypt certificate." + is_expected.to have_body_text help_page_url('user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md', anchor: 'troubleshooting') end end end diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 6f6ff3704b4..80b619ed2b1 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -349,16 +349,13 @@ describe Ci::JobArtifact do end describe 'file is being stored' do - subject { create(:ci_job_artifact, :archive) } - context 'when object has nil store' do - before do - subject.update_column(:file_store, nil) - subject.reload - end - it 'is stored locally' do - expect(subject.file_store).to be(nil) + subject = build(:ci_job_artifact, :archive, file_store: nil) + + subject.save + + expect(subject.file_store).to be(ObjectStorage::Store::LOCAL) expect(subject.file).to be_file_storage expect(subject.file.object_store).to eq(ObjectStorage::Store::LOCAL) end @@ -366,6 +363,10 @@ describe Ci::JobArtifact do context 'when existing object has local store' do it 'is stored locally' do + subject = build(:ci_job_artifact, :archive) + + subject.save + expect(subject.file_store).to be(ObjectStorage::Store::LOCAL) expect(subject.file).to be_file_storage expect(subject.file.object_store).to eq(ObjectStorage::Store::LOCAL) @@ -379,6 +380,10 @@ describe Ci::JobArtifact do context 'when file is stored' do it 'is stored remotely' do + subject = build(:ci_job_artifact, :archive) + + subject.save + expect(subject.file_store).to eq(ObjectStorage::Store::REMOTE) expect(subject.file).not_to be_file_storage expect(subject.file.object_store).to eq(ObjectStorage::Store::REMOTE) diff --git a/spec/models/diff_note_position_spec.rb b/spec/models/diff_note_position_spec.rb new file mode 100644 index 00000000000..a00ba35feef --- /dev/null +++ b/spec/models/diff_note_position_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe DiffNotePosition, type: :model do + it 'has a position attribute' do + diff_position = build(:diff_position) + line_code = 'bd4b7bfff3a247ccf6e3371c41ec018a55230bcc_534_521' + diff_note_position = build(:diff_note_position, line_code: line_code, position: diff_position) + + expect(diff_note_position.position).to eq(diff_position) + expect(diff_note_position.line_code).to eq(line_code) + expect(diff_note_position.diff_content_type).to eq('text') + end + + it 'unique by note_id and diff type' do + existing_diff_note_position = create(:diff_note_position) + diff_note_position = build(:diff_note_position, note: existing_diff_note_position.note) + + expect { diff_note_position.save! }.to raise_error(ActiveRecord::RecordNotUnique) + end +end diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb index d3bd84f1604..fade54f6b11 100644 --- a/spec/requests/api/group_clusters_spec.rb +++ b/spec/requests/api/group_clusters_spec.rb @@ -157,6 +157,7 @@ describe API::GroupClusters do let(:api_url) { 'https://kubernetes.example.com' } let(:authorization_type) { 'rbac' } + let(:management_project_id) { create(:project, group: group).id } let(:platform_kubernetes_attributes) do { @@ -171,7 +172,8 @@ describe API::GroupClusters do name: 'test-cluster', domain: 'domain.example.com', managed: false, - platform_kubernetes_attributes: platform_kubernetes_attributes + platform_kubernetes_attributes: platform_kubernetes_attributes, + management_project_id: management_project_id } end @@ -203,6 +205,7 @@ describe API::GroupClusters do expect(cluster_result.name).to eq('test-cluster') expect(cluster_result.domain).to eq('domain.example.com') expect(cluster_result.managed).to be_falsy + expect(cluster_result.management_project_id).to eq management_project_id expect(platform_kubernetes.rbac?).to be_truthy expect(platform_kubernetes.api_url).to eq(api_url) expect(platform_kubernetes.token).to eq('sample-token') @@ -234,6 +237,18 @@ describe API::GroupClusters do end end + context 'current user does not have access to management_project_id' do + let(:management_project_id) { create(:project).id } + + it 'responds with 400' do + expect(response).to have_gitlab_http_status(:bad_request) + end + + it 'returns validation errors' do + expect(json_response['message']['management_project_id'].first).to match('don\'t have permission') + end + end + context 'with invalid params' do let(:api_url) { 'invalid_api_url' } diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 648577dce8d..ed899e830e1 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -150,6 +150,12 @@ describe API::ProjectClusters do let(:api_url) { 'https://kubernetes.example.com' } let(:namespace) { project.path } let(:authorization_type) { 'rbac' } + let(:management_project) { create(:project, namespace: project.namespace) } + let(:management_project_id) { management_project.id } + + before do + management_project.add_maintainer(current_user) + end let(:platform_kubernetes_attributes) do { @@ -165,7 +171,8 @@ describe API::ProjectClusters do name: 'test-cluster', domain: 'domain.example.com', managed: false, - platform_kubernetes_attributes: platform_kubernetes_attributes + platform_kubernetes_attributes: platform_kubernetes_attributes, + management_project_id: management_project_id } end @@ -194,6 +201,7 @@ describe API::ProjectClusters do expect(cluster_result.name).to eq('test-cluster') expect(cluster_result.domain).to eq('domain.example.com') expect(cluster_result.managed).to be_falsy + expect(cluster_result.management_project_id).to eq management_project_id expect(platform_kubernetes.rbac?).to be_truthy expect(platform_kubernetes.api_url).to eq(api_url) expect(platform_kubernetes.namespace).to eq(namespace) @@ -227,6 +235,18 @@ describe API::ProjectClusters do end end + context 'current user does not have access to management_project_id' do + let(:management_project_id) { create(:project).id } + + it 'responds with 400' do + expect(response).to have_gitlab_http_status(:bad_request) + end + + it 'returns validation errors' do + expect(json_response['message']['management_project_id'].first).to match('don\'t have permission') + end + end + context 'with invalid params' do let(:namespace) { 'invalid_namespace' } diff --git a/spec/rubocop/cop/performance/ar_count_each_spec.rb b/spec/rubocop/cop/performance/ar_count_each_spec.rb new file mode 100644 index 00000000000..f934a1fde48 --- /dev/null +++ b/spec/rubocop/cop/performance/ar_count_each_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require_relative '../../../support/helpers/expect_offense' +require_relative '../../../../rubocop/cop/performance/ar_count_each.rb' + +describe RuboCop::Cop::Performance::ARCountEach do + include CopHelper + include ExpectOffense + + subject(:cop) { described_class.new } + + context 'when it is not haml file' do + it 'does not flag it as an offense' do + expect(subject).to receive(:in_haml_file?).with(anything).at_least(:once).and_return(false) + + expect_no_offenses <<~SOURCE + show(@users.count) + @users.each { |user| display(user) } + SOURCE + end + end + + context 'when it is haml file' do + before do + expect(subject).to receive(:in_haml_file?).with(anything).at_least(:once).and_return(true) + end + + context 'when the same object uses count and each' do + it 'flags it as an offense' do + expect_offense <<~SOURCE + show(@users.count) + ^^^^^^^^^^^^ If @users is AR relation, avoid `@users.count ...; @users.each... `, this will trigger two queries. Use `@users.load.size ...; @users.each... ` instead. If @users is an array, try to use @users.size. + @users.each { |user| display(user) } + SOURCE + + expect(cop.offenses.map(&:cop_name)).to contain_exactly('Performance/ARCountEach') + end + end + + context 'when different object uses count and each' do + it 'does not flag it as an offense' do + expect_no_offenses <<~SOURCE + show(@emails.count) + @users.each { |user| display(user) } + SOURCE + end + end + + context 'when just using count without each' do + it 'does not flag it as an offense' do + expect_no_offenses '@users.count' + end + end + + context 'when just using each without count' do + it 'does not flag it as an offense' do + expect_no_offenses '@users.each { |user| display(user) }' + end + end + end +end diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb index 6e1fdb7aad0..069572e4dff 100644 --- a/spec/services/application_settings/update_service_spec.rb +++ b/spec/services/application_settings/update_service_spec.rb @@ -334,4 +334,20 @@ describe ApplicationSettings::UpdateService do expect(application_settings.protected_paths).to eq(['/users/password', '/users/sign_in']) end end + + context 'when issues_create_limit is passsed' do + let(:params) do + { + issues_create_limit: 600 + } + end + + it 'updates issues_create_limit value' do + subject.execute + + application_settings.reload + + expect(application_settings.issues_create_limit).to eq(600) + end + end end diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb index ecf0a9c9dce..3dd25be2a3d 100644 --- a/spec/services/clusters/create_service_spec.rb +++ b/spec/services/clusters/create_service_spec.rb @@ -59,4 +59,92 @@ describe Clusters::CreateService do end end end + + context 'when params includes :management_project_id' do + subject(:cluster) { described_class.new(user, params).execute(access_token: access_token) } + + let(:params) do + { + name: 'test-cluster', + provider_type: :gcp, + provider_gcp_attributes: { + gcp_project_id: 'gcp-project', + zone: 'us-central1-a', + num_nodes: 1, + machine_type: 'machine_type-a', + legacy_abac: 'true' + }, + clusterable: clusterable, + management_project_id: management_project_id + } + end + + let(:clusterable) { project } + let(:management_project_id) { management_project.id } + let(:management_project_namespace) { project.namespace } + let(:management_project) { create(:project, namespace: management_project_namespace) } + + shared_examples 'invalid project or cluster permissions' do + it 'does not persist the cluster and adds errors' do + expect(cluster).not_to be_persisted + + expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action') + end + end + + shared_examples 'setting a management project' do + context 'when user is authorized to adminster manangement_project' do + before do + management_project.add_maintainer(user) + end + + it 'persists the cluster' do + expect(cluster).to be_persisted + + expect(cluster.management_project).to eq(management_project) + end + end + + context 'when user is not authorized to adminster manangement_project' do + include_examples 'invalid project or cluster permissions' + end + end + + shared_examples 'setting a management project outside of scope' do + context 'when manangement_project is outside of the namespace scope' do + let(:management_project_namespace) { create(:group) } + + it 'does not persist the cluster' do + expect(cluster).not_to be_persisted + + expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action') + end + end + end + + context 'management_project is non-existent' do + let(:management_project_id) { 0 } + + include_examples 'invalid project or cluster permissions' + end + + context 'project cluster' do + include_examples 'setting a management project' + include_examples 'setting a management project outside of scope' + end + + context 'group cluster' do + let(:management_project_namespace) { create(:group) } + let(:clusterable) { management_project_namespace } + + include_examples 'setting a management project' + include_examples 'setting a management project outside of scope' + end + + context 'instance cluster' do + let(:clusterable) { Clusters::Instance.new } + + include_examples 'setting a management project' + end + end end diff --git a/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb b/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb new file mode 100644 index 00000000000..1bcebe2e2ac --- /dev/null +++ b/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Management::ValidateManagementProjectPermissionsService do + describe '#execute' do + subject { described_class.new(user).execute(cluster, management_project_id) } + + let(:cluster) { build(:cluster, :project, projects: [create(:project)]) } + let(:user) { create(:user) } + + context 'when management_project_id is nil' do + let(:management_project_id) { nil } + + it { is_expected.to be true } + end + + context 'when management_project_id is not nil' do + let(:management_project_id) { management_project.id } + let(:management_project_namespace) { create(:group) } + let(:management_project) { create(:project, namespace: management_project_namespace) } + + context 'when management_project does not exist' do + let(:management_project_id) { 0 } + + it 'adds errors to the cluster and returns false' do + is_expected.to eq false + + expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action') + end + end + + shared_examples 'management project is in scope' do + context 'when user is authorized to administer manangement_project' do + before do + management_project.add_maintainer(user) + end + + it 'adds no error and returns true' do + is_expected.to eq true + + expect(cluster.errors).to be_empty + end + end + + context 'when user is not authorized to adminster manangement_project' do + it 'adds an error and returns false' do + is_expected.to eq false + + expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action') + end + end + end + + shared_examples 'management project is out of scope' do + context 'when manangement_project is outside of the namespace scope' do + let(:management_project_namespace) { create(:group) } + + it 'adds an error and returns false' do + is_expected.to eq false + + expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action') + end + end + end + + context 'project cluster' do + let(:cluster) { build(:cluster, :project, projects: [create(:project, namespace: management_project_namespace)]) } + + include_examples 'management project is in scope' + include_examples 'management project is out of scope' + end + + context 'group cluster' do + let(:cluster) { build(:cluster, :group, groups: [management_project_namespace]) } + + include_examples 'management project is in scope' + include_examples 'management project is out of scope' + end + + context 'instance cluster' do + let(:cluster) { build(:cluster, :instance) } + + include_examples 'management project is in scope' + end + end + end +end diff --git a/spec/services/environments/auto_stop_service_spec.rb b/spec/services/environments/auto_stop_service_spec.rb index 3620bf8fe87..b34d15889d3 100644 --- a/spec/services/environments/auto_stop_service_spec.rb +++ b/spec/services/environments/auto_stop_service_spec.rb @@ -40,18 +40,6 @@ describe Environments::AutoStopService, :clean_gitlab_redis_shared_state do expect(Ci::Build.where(name: 'stop_review_app').map(&:status).uniq).to eq(['pending']) end - context 'when auto_stop_environments feature flag is disabled' do - before do - stub_feature_flags(auto_stop_environments: false) - end - - it 'does not execute Ci::StopEnvironmentsService' do - expect(Ci::StopEnvironmentsService).not_to receive(:execute_in_batch) - - subject - end - end - context 'when the other sidekiq worker has already been running' do before do stub_exclusive_lease_taken(described_class::EXCLUSIVE_LOCK_KEY) diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 86f37e9204c..163ca0b9bc3 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2604,6 +2604,7 @@ describe NotificationService, :mailer do pages_domain_disabled pages_domain_verification_succeeded pages_domain_verification_failed + pages_domain_auto_ssl_failed ].each do |sym| describe "##{sym}" do subject(:notify!) { notification.send(sym, domain) } diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb index 163276db7e6..63fd0978c97 100644 --- a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb +++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb @@ -180,5 +180,13 @@ describe PagesDomains::ObtainLetsEncryptCertificateService do expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil end + + it 'sends notification' do + expect_next_instance_of(NotificationService) do |notification_service| + expect(notification_service).to receive(:pages_domain_auto_ssl_failed).with(pages_domain) + end + + service.execute + end end end diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb index 71eff23c77c..140595e58ad 100644 --- a/spec/uploaders/records_uploads_spec.rb +++ b/spec/uploaders/records_uploads_spec.rb @@ -78,7 +78,8 @@ describe RecordsUploads do path: File.join('uploads', 'rails_sample.jpg'), size: 512.kilobytes, model: build_stubbed(:user), - uploader: uploader.class.to_s + uploader: uploader.class.to_s, + store: ::ObjectStorage::Store::LOCAL ) uploader.upload = existing @@ -98,7 +99,8 @@ describe RecordsUploads do path: File.join('uploads', 'rails_sample.jpg'), size: 512.kilobytes, model: project, - uploader: uploader.class.to_s + uploader: uploader.class.to_s, + store: ::ObjectStorage::Store::LOCAL ) uploader.store!(upload_fixture('rails_sample.jpg')) diff --git a/spec/views/projects/pages/show.html.haml_spec.rb b/spec/views/projects/pages/show.html.haml_spec.rb index 80410e7bc32..39384484279 100644 --- a/spec/views/projects/pages/show.html.haml_spec.rb +++ b/spec/views/projects/pages/show.html.haml_spec.rb @@ -17,7 +17,7 @@ describe 'projects/pages/show' do assign(:project, project) allow(view).to receive(:current_user).and_return(user) - assign(:domains, [domain]) + assign(:domains, project.pages_domains) end describe 'validation warning' do |