diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-16 18:08:01 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-16 18:08:01 +0000 |
commit | 8e45d25f7dde6508839ffee719c0ddc2cf6b12d3 (patch) | |
tree | 9839e7fe63b36904d40995ebf519124c9a8f7681 /spec | |
parent | 00c78fb814d7ce00989ac04edd6cdaa3239da284 (diff) | |
download | gitlab-ce-8e45d25f7dde6508839ffee719c0ddc2cf6b12d3.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
34 files changed, 1912 insertions, 124 deletions
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb index b9ee69a617b..66112c95742 100644 --- a/spec/controllers/projects/deployments_controller_spec.rb +++ b/spec/controllers/projects/deployments_controller_spec.rb @@ -75,15 +75,13 @@ describe Projects::DeploymentsController do } end - before do + it 'returns a metrics JSON document' do expect_next_instance_of(DeploymentMetrics) do |deployment_metrics| allow(deployment_metrics).to receive(:has_metrics?).and_return(true) expect(deployment_metrics).to receive(:metrics).and_return(empty_metrics) end - end - it 'returns a metrics JSON document' do get :metrics, params: deployment_params(id: deployment.to_param) expect(response).to be_ok @@ -91,6 +89,19 @@ describe Projects::DeploymentsController do expect(json_response['metrics']).to eq({}) expect(json_response['last_update']).to eq(42) end + + it 'returns a 404 if the deployment failed' do + failed_deployment = create( + :deployment, + :failed, + project: project, + environment: environment + ) + + get :metrics, params: deployment_params(id: failed_deployment.to_param) + + expect(response).to have_gitlab_http_status(404) + end end end end diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index e677e836145..5c02e8d6461 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -82,9 +82,9 @@ describe Projects::MergeRequests::DiffsController do end end - context 'when note has no position' do + context 'when note is a legacy diff note' do before do - create(:legacy_diff_note_on_merge_request, project: project, noteable: merge_request, position: nil) + create(:legacy_diff_note_on_merge_request, project: project, noteable: merge_request) end it 'serializes merge request diff collection' do diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 25823b75d18..dd690699ff6 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -66,8 +66,8 @@ describe 'Environment' do create(:deployment, :running, environment: environment, deployable: build) end - it 'does not show deployments' do - expect(page).to have_content('You don\'t have any deployments right now.') + it 'does show deployments' do + expect(page).to have_link("#{build.name} (##{build.id})") end end @@ -79,8 +79,8 @@ describe 'Environment' do create(:deployment, :failed, environment: environment, deployable: build) end - it 'does not show deployments' do - expect(page).to have_content('You don\'t have any deployments right now.') + it 'does show deployments' do + expect(page).to have_link("#{build.name} (##{build.id})") end end @@ -175,7 +175,7 @@ describe 'Environment' do # # In EE we have to stub EE::Environment since it overwrites # the "terminals" method. - allow_any_instance_of(defined?(EE) ? EE::Environment : Environment) + allow_any_instance_of(Gitlab.ee? ? EE::Environment : Environment) .to receive(:terminals) { nil } visit terminal_project_environment_path(project, environment) diff --git a/spec/fixtures/api/schemas/deployment.json b/spec/fixtures/api/schemas/deployment.json index b1e3c000ddf..0cfeadfe548 100644 --- a/spec/fixtures/api/schemas/deployment.json +++ b/spec/fixtures/api/schemas/deployment.json @@ -61,7 +61,7 @@ "type": "array", "items": { "$ref": "job/job.json" } }, - "status": { "type": "string" } + "status": { "type": "string" } }, "additionalProperties": false } diff --git a/spec/frontend/releases/detail/components/app_spec.js b/spec/frontend/releases/detail/components/app_spec.js new file mode 100644 index 00000000000..f8eb33a69a8 --- /dev/null +++ b/spec/frontend/releases/detail/components/app_spec.js @@ -0,0 +1,70 @@ +import Vuex from 'vuex'; +import { mount } from '@vue/test-utils'; +import ReleaseDetailApp from '~/releases/detail/components/app'; +import { release } from '../../mock_data'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; + +describe('Release detail component', () => { + let wrapper; + let releaseClone; + let actions; + + beforeEach(() => { + gon.api_version = 'v4'; + + releaseClone = JSON.parse(JSON.stringify(convertObjectPropsToCamelCase(release))); + + const state = { + release: releaseClone, + markdownDocsPath: 'path/to/markdown/docs', + }; + + actions = { + fetchRelease: jest.fn(), + updateRelease: jest.fn(), + navigateToReleasesPage: jest.fn(), + }; + + const store = new Vuex.Store({ actions, state }); + + wrapper = mount(ReleaseDetailApp, { store }); + + return wrapper.vm.$nextTick(); + }); + + it('calls fetchRelease when the component is created', () => { + expect(actions.fetchRelease).toHaveBeenCalledTimes(1); + }); + + it('renders the description text at the top of the page', () => { + expect(wrapper.find('.js-subtitle-text').text()).toBe( + 'Releases are based on Git tags. We recommend naming tags that fit within semantic versioning, for example v1.0, v2.0-pre.', + ); + }); + + it('renders the correct tag name in the "Tag name" field', () => { + expect(wrapper.find('#git-ref').element.value).toBe(releaseClone.tagName); + }); + + it('renders the correct release title in the "Release title" field', () => { + expect(wrapper.find('#release-title').element.value).toBe(releaseClone.name); + }); + + it('renders the release notes in the "Release notes" textarea', () => { + expect(wrapper.find('#release-notes').element.value).toBe(releaseClone.description); + }); + + it('renders the "Save changes" button as type="submit"', () => { + expect(wrapper.find('.js-submit-button').attributes('type')).toBe('submit'); + }); + + it('calls updateRelease when the form is submitted', () => { + wrapper.find('form').trigger('submit'); + expect(actions.updateRelease).toHaveBeenCalledTimes(1); + }); + + it('calls navigateToReleasesPage when the "Cancel" button is clicked', () => { + wrapper.find('.js-cancel-button').vm.$emit('click'); + expect(actions.navigateToReleasesPage).toHaveBeenCalledTimes(1); + }); +}); diff --git a/spec/frontend/releases/detail/store/actions_spec.js b/spec/frontend/releases/detail/store/actions_spec.js new file mode 100644 index 00000000000..f1c7f3c1048 --- /dev/null +++ b/spec/frontend/releases/detail/store/actions_spec.js @@ -0,0 +1,217 @@ +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import * as actions from '~/releases/detail/store/actions'; +import testAction from 'helpers/vuex_action_helper'; +import * as types from '~/releases/detail/store/mutation_types'; +import { release } from '../../mock_data'; +import state from '~/releases/detail/store/state'; +import createFlash from '~/flash'; +import { redirectTo } from '~/lib/utils/url_utility'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; + +jest.mock('~/flash', () => jest.fn()); + +jest.mock('~/lib/utils/url_utility', () => ({ + redirectTo: jest.fn(), + joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths, +})); + +describe('Release detail actions', () => { + let stateClone; + let releaseClone; + let mock; + let error; + + beforeEach(() => { + stateClone = state(); + releaseClone = JSON.parse(JSON.stringify(release)); + mock = new MockAdapter(axios); + gon.api_version = 'v4'; + error = { message: 'An error occurred' }; + createFlash.mockClear(); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('setInitialState', () => { + it(`commits ${types.SET_INITIAL_STATE} with the provided object`, () => { + const initialState = {}; + + return testAction(actions.setInitialState, initialState, stateClone, [ + { type: types.SET_INITIAL_STATE, payload: initialState }, + ]); + }); + }); + + describe('requestRelease', () => { + it(`commits ${types.REQUEST_RELEASE}`, () => + testAction(actions.requestRelease, undefined, stateClone, [{ type: types.REQUEST_RELEASE }])); + }); + + describe('receiveReleaseSuccess', () => { + it(`commits ${types.RECEIVE_RELEASE_SUCCESS}`, () => + testAction(actions.receiveReleaseSuccess, releaseClone, stateClone, [ + { type: types.RECEIVE_RELEASE_SUCCESS, payload: releaseClone }, + ])); + }); + + describe('receiveReleaseError', () => { + it(`commits ${types.RECEIVE_RELEASE_ERROR}`, () => + testAction(actions.receiveReleaseError, error, stateClone, [ + { type: types.RECEIVE_RELEASE_ERROR, payload: error }, + ])); + + it('shows a flash with an error message', () => { + actions.receiveReleaseError({ commit: jest.fn() }, error); + + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith( + 'Something went wrong while getting the release details', + ); + }); + }); + + describe('fetchRelease', () => { + let getReleaseUrl; + + beforeEach(() => { + stateClone.projectId = '18'; + stateClone.tagName = 'v1.3'; + getReleaseUrl = `/api/v4/projects/${stateClone.projectId}/releases/${stateClone.tagName}`; + }); + + it(`dispatches requestRelease and receiveReleaseSuccess with the camel-case'd release object`, () => { + mock.onGet(getReleaseUrl).replyOnce(200, releaseClone); + + return testAction( + actions.fetchRelease, + undefined, + stateClone, + [], + [ + { type: 'requestRelease' }, + { + type: 'receiveReleaseSuccess', + payload: convertObjectPropsToCamelCase(releaseClone, { deep: true }), + }, + ], + ); + }); + + it(`dispatches requestRelease and receiveReleaseError with an error object`, () => { + mock.onGet(getReleaseUrl).replyOnce(500); + + return testAction( + actions.fetchRelease, + undefined, + stateClone, + [], + [{ type: 'requestRelease' }, { type: 'receiveReleaseError', payload: expect.anything() }], + ); + }); + }); + + describe('updateReleaseTitle', () => { + it(`commits ${types.UPDATE_RELEASE_TITLE} with the updated release title`, () => { + const newTitle = 'The new release title'; + return testAction(actions.updateReleaseTitle, newTitle, stateClone, [ + { type: types.UPDATE_RELEASE_TITLE, payload: newTitle }, + ]); + }); + }); + + describe('updateReleaseNotes', () => { + it(`commits ${types.UPDATE_RELEASE_NOTES} with the updated release notes`, () => { + const newReleaseNotes = 'The new release notes'; + return testAction(actions.updateReleaseNotes, newReleaseNotes, stateClone, [ + { type: types.UPDATE_RELEASE_NOTES, payload: newReleaseNotes }, + ]); + }); + }); + + describe('requestUpdateRelease', () => { + it(`commits ${types.REQUEST_UPDATE_RELEASE}`, () => + testAction(actions.requestUpdateRelease, undefined, stateClone, [ + { type: types.REQUEST_UPDATE_RELEASE }, + ])); + }); + + describe('receiveUpdateReleaseSuccess', () => { + it(`commits ${types.RECEIVE_UPDATE_RELEASE_SUCCESS}`, () => + testAction( + actions.receiveUpdateReleaseSuccess, + undefined, + stateClone, + [{ type: types.RECEIVE_UPDATE_RELEASE_SUCCESS }], + [{ type: 'navigateToReleasesPage' }], + )); + }); + + describe('receiveUpdateReleaseError', () => { + it(`commits ${types.RECEIVE_UPDATE_RELEASE_ERROR}`, () => + testAction(actions.receiveUpdateReleaseError, error, stateClone, [ + { type: types.RECEIVE_UPDATE_RELEASE_ERROR, payload: error }, + ])); + + it('shows a flash with an error message', () => { + actions.receiveUpdateReleaseError({ commit: jest.fn() }, error); + + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith( + 'Something went wrong while saving the release details', + ); + }); + }); + + describe('updateRelease', () => { + let getReleaseUrl; + + beforeEach(() => { + stateClone.release = releaseClone; + stateClone.projectId = '18'; + stateClone.tagName = 'v1.3'; + getReleaseUrl = `/api/v4/projects/${stateClone.projectId}/releases/${stateClone.tagName}`; + }); + + it(`dispatches requestUpdateRelease and receiveUpdateReleaseSuccess`, () => { + mock.onPut(getReleaseUrl).replyOnce(200); + + return testAction( + actions.updateRelease, + undefined, + stateClone, + [], + [{ type: 'requestUpdateRelease' }, { type: 'receiveUpdateReleaseSuccess' }], + ); + }); + + it(`dispatches requestUpdateRelease and receiveUpdateReleaseError with an error object`, () => { + mock.onPut(getReleaseUrl).replyOnce(500); + + return testAction( + actions.updateRelease, + undefined, + stateClone, + [], + [ + { type: 'requestUpdateRelease' }, + { type: 'receiveUpdateReleaseError', payload: expect.anything() }, + ], + ); + }); + }); + + describe('navigateToReleasesPage', () => { + it(`calls redirectTo() with the URL to the releases page`, () => { + const releasesPagePath = 'path/to/releases/page'; + stateClone.releasesPagePath = releasesPagePath; + + actions.navigateToReleasesPage({ state: stateClone }); + + expect(redirectTo).toHaveBeenCalledTimes(1); + expect(redirectTo).toHaveBeenCalledWith(releasesPagePath); + }); + }); +}); diff --git a/spec/frontend/releases/detail/store/mutations_spec.js b/spec/frontend/releases/detail/store/mutations_spec.js new file mode 100644 index 00000000000..106a40c812e --- /dev/null +++ b/spec/frontend/releases/detail/store/mutations_spec.js @@ -0,0 +1,119 @@ +/* eslint-disable jest/valid-describe */ +/* + * ESLint disable directive ↑ can be removed once + * https://github.com/jest-community/eslint-plugin-jest/issues/203 + * is resolved + */ + +import state from '~/releases/detail/store/state'; +import mutations from '~/releases/detail/store/mutations'; +import * as types from '~/releases/detail/store/mutation_types'; +import { release } from '../../mock_data'; + +describe('Release detail mutations', () => { + let stateClone; + let releaseClone; + + beforeEach(() => { + stateClone = state(); + releaseClone = JSON.parse(JSON.stringify(release)); + }); + + describe(types.SET_INITIAL_STATE, () => { + it('populates the state with initial values', () => { + const initialState = { + projectId: '18', + tagName: 'v1.3', + releasesPagePath: 'path/to/releases/page', + markdownDocsPath: 'path/to/markdown/docs', + markdownPreviewPath: 'path/to/markdown/preview', + }; + + mutations[types.SET_INITIAL_STATE](stateClone, initialState); + + expect(stateClone).toEqual(expect.objectContaining(initialState)); + }); + }); + + describe(types.REQUEST_RELEASE, () => { + it('set state.isFetchingRelease to true', () => { + mutations[types.REQUEST_RELEASE](stateClone); + + expect(stateClone.isFetchingRelease).toEqual(true); + }); + }); + + describe(types.RECEIVE_RELEASE_SUCCESS, () => { + it('handles a successful response from the server', () => { + mutations[types.RECEIVE_RELEASE_SUCCESS](stateClone, releaseClone); + + expect(stateClone.fetchError).toEqual(undefined); + + expect(stateClone.isFetchingRelease).toEqual(false); + + expect(stateClone.release).toEqual(releaseClone); + }); + }); + + describe(types.RECEIVE_RELEASE_ERROR, () => { + it('handles an unsuccessful response from the server', () => { + const error = { message: 'An error occurred!' }; + mutations[types.RECEIVE_RELEASE_ERROR](stateClone, error); + + expect(stateClone.isFetchingRelease).toEqual(false); + + expect(stateClone.release).toBeUndefined(); + + expect(stateClone.fetchError).toEqual(error); + }); + }); + + describe(types.UPDATE_RELEASE_TITLE, () => { + it("updates the release's title", () => { + stateClone.release = releaseClone; + const newTitle = 'The new release title'; + mutations[types.UPDATE_RELEASE_TITLE](stateClone, newTitle); + + expect(stateClone.release.name).toEqual(newTitle); + }); + }); + + describe(types.UPDATE_RELEASE_NOTES, () => { + it("updates the release's notes", () => { + stateClone.release = releaseClone; + const newNotes = 'The new release notes'; + mutations[types.UPDATE_RELEASE_NOTES](stateClone, newNotes); + + expect(stateClone.release.description).toEqual(newNotes); + }); + }); + + describe(types.REQUEST_UPDATE_RELEASE, () => { + it('set state.isUpdatingRelease to true', () => { + mutations[types.REQUEST_UPDATE_RELEASE](stateClone); + + expect(stateClone.isUpdatingRelease).toEqual(true); + }); + }); + + describe(types.RECEIVE_UPDATE_RELEASE_SUCCESS, () => { + it('handles a successful response from the server', () => { + mutations[types.RECEIVE_UPDATE_RELEASE_SUCCESS](stateClone, releaseClone); + + expect(stateClone.updateError).toEqual(undefined); + + expect(stateClone.isUpdatingRelease).toEqual(false); + }); + }); + + describe(types.RECEIVE_UPDATE_RELEASE_ERROR, () => { + it('handles an unsuccessful response from the server', () => { + const error = { message: 'An error occurred!' }; + mutations[types.RECEIVE_UPDATE_RELEASE_ERROR](stateClone, error); + + expect(stateClone.isUpdatingRelease).toEqual(false); + + expect(stateClone.updateError).toEqual(error); + }); + }); +}); diff --git a/spec/helpers/environment_helper_spec.rb b/spec/helpers/environment_helper_spec.rb new file mode 100644 index 00000000000..53953d72b06 --- /dev/null +++ b/spec/helpers/environment_helper_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe EnvironmentHelper do + describe '#render_deployment_status' do + context 'when using a manual deployment' do + it 'renders a span tag' do + deploy = build(:deployment, deployable: nil, status: :success) + html = helper.render_deployment_status(deploy) + + expect(html).to have_css('span.ci-status.ci-success') + end + end + + context 'when using a deployment from a build' do + it 'renders a link tag' do + deploy = build(:deployment, status: :success) + html = helper.render_deployment_status(deploy) + + expect(html).to have_css('a.ci-status.ci-success') + end + end + end +end diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 191df3cc709..cb6b158f01c 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -70,7 +70,7 @@ window.gl = window.gl || {}; window.gl.TEST_HOST = TEST_HOST; window.gon = window.gon || {}; window.gon.test_env = true; -window.gon.ee = process.env.IS_GITLAB_EE; +window.gon.ee = process.env.IS_EE; gon.relative_url_root = ''; let hasUnhandledPromiseRejections = false; @@ -118,7 +118,7 @@ const axiosDefaultAdapter = getDefaultAdapter(); // render all of our tests const testContexts = [require.context('spec', true, /_spec$/)]; -if (process.env.IS_GITLAB_EE) { +if (process.env.IS_EE) { testContexts.push(require.context('ee_spec', true, /_spec$/)); } @@ -207,7 +207,7 @@ if (process.env.BABEL_ENV === 'coverage') { describe('Uncovered files', function() { const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)]; - if (process.env.IS_GITLAB_EE) { + if (process.env.IS_EE) { sourceFilesContexts.push(require.context('ee', true, /\.(js|vue)$/)); } diff --git a/spec/lib/gitlab/ci/ansi2json/line_spec.rb b/spec/lib/gitlab/ci/ansi2json/line_spec.rb new file mode 100644 index 00000000000..4b5c3f9489e --- /dev/null +++ b/spec/lib/gitlab/ci/ansi2json/line_spec.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Ansi2json::Line do + let(:offset) { 0 } + let(:style) { Gitlab::Ci::Ansi2json::Style.new } + + subject { described_class.new(offset: offset, style: style) } + + describe '#<<' do + it 'appends new data to the current segment' do + expect { subject << 'test 1' }.to change { subject.current_segment.text } + expect(subject.current_segment.text).to eq('test 1') + + expect { subject << ', test 2' }.to change { subject.current_segment.text } + expect(subject.current_segment.text).to eq('test 1, test 2') + end + end + + describe '#style' do + context 'when style is passed to the initializer' do + let(:style) { double } + + it 'returns the same style' do + expect(subject.style).to eq(style) + end + end + + context 'when style is not passed to the initializer' do + it 'returns the default style' do + expect(subject.style.set?).to be_falsey + end + end + end + + describe '#update_style' do + let(:expected_style) do + Gitlab::Ci::Ansi2json::Style.new( + fg: 'term-fg-l-yellow', + bg: 'term-bg-blue', + mask: 1) + end + + it 'sets the style' do + subject.update_style(%w[1 33 44]) + + expect(subject.style).to eq(expected_style) + end + end + + describe '#add_section' do + it 'appends a new section to the list' do + subject.add_section('section_1') + subject.add_section('section_2') + + expect(subject.sections).to eq(%w[section_1 section_2]) + end + end + + describe '#set_as_section_header' do + it 'change the section_header to true' do + expect { subject.set_as_section_header } + .to change { subject.section_header } + .to be_truthy + end + end + + describe '#set_section_duration' do + it 'sets and formats the section_duration' do + subject.set_section_duration(75) + + expect(subject.section_duration).to eq('01:15') + end + end + + describe '#flush_current_segment!' do + context 'when current segment is not empty' do + before do + subject << 'some data' + end + + it 'adds the segment to the list' do + expect { subject.flush_current_segment! }.to change { subject.segments.count }.by(1) + + expect(subject.segments.map { |s| s[:text] }).to eq(['some data']) + end + + it 'updates the current segment pointer propagating the style' do + previous_segment = subject.current_segment + + subject.flush_current_segment! + + expect(subject.current_segment).not_to eq(previous_segment) + expect(subject.current_segment.style).to eq(previous_segment.style) + end + end + + context 'when current segment is empty' do + it 'does not add any segments to the list' do + expect { subject.flush_current_segment! }.not_to change { subject.segments.count } + end + + it 'does not change the current segment' do + expect { subject.flush_current_segment! }.not_to change { subject.current_segment } + end + end + end + + describe '#to_h' do + before do + subject << 'some data' + subject.update_style(['1']) + end + + context 'when sections are present' do + before do + subject.add_section('section_1') + subject.add_section('section_2') + end + + context 'when section header is set' do + before do + subject.set_as_section_header + end + + it 'serializes the attributes set' do + result = { + offset: 0, + content: [{ text: 'some data', style: 'term-bold' }], + section: 'section_2', + section_header: true + } + + expect(subject.to_h).to eq(result) + end + end + + context 'when section duration is set' do + before do + subject.set_section_duration(75) + end + + it 'serializes the attributes set' do + result = { + offset: 0, + content: [{ text: 'some data', style: 'term-bold' }], + section: 'section_2', + section_duration: '01:15' + } + + expect(subject.to_h).to eq(result) + end + end + end + + context 'when there are no sections' do + it 'serializes the attributes set' do + result = { + offset: 0, + content: [{ text: 'some data', style: 'term-bold' }] + } + + expect(subject.to_h).to eq(result) + end + end + end +end diff --git a/spec/lib/gitlab/ci/ansi2json/parser_spec.rb b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb new file mode 100644 index 00000000000..e161e74c1ff --- /dev/null +++ b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# The rest of the specs for this class are covered in style_spec.rb +describe Gitlab::Ci::Ansi2json::Parser do + subject { described_class } + + describe 'bold?' do + it 'returns true if style mask matches bold format' do + expect(subject.bold?(0x01)).to be_truthy + end + + it 'returns false if style mask does not match bold format' do + expect(subject.bold?(0x02)).to be_falsey + end + end + + describe 'matching_formats' do + it 'returns matching formats given a style mask' do + expect(subject.matching_formats(0x01)).to eq(%w[term-bold]) + expect(subject.matching_formats(0x03)).to eq(%w[term-bold term-italic]) + expect(subject.matching_formats(0x07)).to eq(%w[term-bold term-italic term-underline]) + end + + it 'returns an empty array if no formats match the style mask' do + expect(subject.matching_formats(0)).to eq([]) + end + end +end diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb new file mode 100644 index 00000000000..88a0ca35859 --- /dev/null +++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Ansi2json::Style do + describe '#set?' do + subject { described_class.new(params).set? } + + context 'when fg color is set' do + let(:params) { { fg: 'term-fg-black' } } + + it { is_expected.to be_truthy } + end + + context 'when bg color is set' do + let(:params) { { bg: 'term-bg-black' } } + + it { is_expected.to be_truthy } + end + + context 'when mask is set' do + let(:params) { { mask: 0x01 } } + + it { is_expected.to be_truthy } + end + + context 'nothing is set' do + let(:params) { {} } + + it { is_expected.to be_falsey } + end + end + + describe '#reset!' do + let(:style) { described_class.new(fg: 'term-fg-black', bg: 'term-bg-yellow', mask: 0x01) } + + it 'set the style params to default' do + style.reset! + + expect(style.fg).to be_nil + expect(style.bg).to be_nil + expect(style.mask).to be_zero + end + end + + describe 'update formats to mimic terminals' do + subject { described_class.new(params) } + + context 'when fg color present' do + let(:params) { { fg: 'term-fg-black', mask: mask } } + + context 'when mask is set to bold' do + let(:mask) { 0x01 } + + it 'changes the fg color to a lighter version' do + expect(subject.fg).to eq('term-fg-l-black') + end + end + + context 'when mask set to another format' do + let(:mask) { 0x02 } + + it 'does not change the fg color' do + expect(subject.fg).to eq('term-fg-black') + end + end + + context 'when mask is not set' do + let(:mask) { 0 } + + it 'does not change the fg color' do + expect(subject.fg).to eq('term-fg-black') + end + end + end + end + + describe '#update' do + where(:initial_state, :ansi_commands, :result, :description) do + [ + # add format + [[], %w[0], '', 'does not set any style'], + [[], %w[1], 'term-bold', 'enables format bold'], + [[], %w[3], 'term-italic', 'enables format italic'], + [[], %w[4], 'term-underline', 'enables format underline'], + [[], %w[8], 'term-conceal', 'enables format conceal'], + [[], %w[9], 'term-cross', 'enables format cross'], + # remove format + [%w[1], %w[21], '', 'disables format bold'], + [%w[1 3], %w[21], 'term-italic', 'disables format bold and leaves italic'], + [%w[1], %w[22], '', 'disables format bold using command 22'], + [%w[1 3], %w[22], 'term-italic', 'disables format bold and leaves italic using command 22'], + [%w[3], %w[23], '', 'disables format italic'], + [%w[1 3], %w[23], 'term-bold', 'disables format italic and leaves bold'], + [%w[4], %w[24], '', 'disables format underline'], + [%w[1 4], %w[24], 'term-bold', 'disables format underline and leaves bold'], + [%w[8], %w[28], '', 'disables format conceal'], + [%w[1 8], %w[28], 'term-bold', 'disables format conceal and leaves bold'], + [%w[9], %w[29], '', 'disables format cross'], + [%w[1 9], %w[29], 'term-bold', 'disables format cross and leaves bold'], + # set fg color + [[], %w[30], 'term-fg-black', 'sets fg color black'], + [[], %w[31], 'term-fg-red', 'sets fg color red'], + [[], %w[32], 'term-fg-green', 'sets fg color green'], + [[], %w[33], 'term-fg-yellow', 'sets fg color yellow'], + [[], %w[34], 'term-fg-blue', 'sets fg color blue'], + [[], %w[35], 'term-fg-magenta', 'sets fg color magenta'], + [[], %w[36], 'term-fg-cyan', 'sets fg color cyan'], + [[], %w[37], 'term-fg-white', 'sets fg color white'], + # sets xterm fg color + [[], %w[38 5 1], 'xterm-fg-1', 'sets xterm fg color 1'], + [[], %w[38 5 2], 'xterm-fg-2', 'sets xterm fg color 2'], + [[], %w[38 1], 'term-bold', 'ignores 38 command if not followed by 5 and sets format bold'], + # set bg color + [[], %w[40], 'term-bg-black', 'sets bg color black'], + [[], %w[41], 'term-bg-red', 'sets bg color red'], + [[], %w[42], 'term-bg-green', 'sets bg color green'], + [[], %w[43], 'term-bg-yellow', 'sets bg color yellow'], + [[], %w[44], 'term-bg-blue', 'sets bg color blue'], + [[], %w[45], 'term-bg-magenta', 'sets bg color magenta'], + [[], %w[46], 'term-bg-cyan', 'sets bg color cyan'], + [[], %w[47], 'term-bg-white', 'sets bg color white'], + # set xterm bg color + [[], %w[48 5 1], 'xterm-bg-1', 'sets xterm bg color 1'], + [[], %w[48 5 2], 'xterm-bg-2', 'sets xterm bg color 2'], + [[], %w[48 1], 'term-bold', 'ignores 48 command if not followed by 5 and sets format bold'], + # set light fg color + [[], %w[90], 'term-fg-l-black', 'sets fg color light black'], + [[], %w[91], 'term-fg-l-red', 'sets fg color light red'], + [[], %w[92], 'term-fg-l-green', 'sets fg color light green'], + [[], %w[93], 'term-fg-l-yellow', 'sets fg color light yellow'], + [[], %w[94], 'term-fg-l-blue', 'sets fg color light blue'], + [[], %w[95], 'term-fg-l-magenta', 'sets fg color light magenta'], + [[], %w[96], 'term-fg-l-cyan', 'sets fg color light cyan'], + [[], %w[97], 'term-fg-l-white', 'sets fg color light white'], + # set light bg color + [[], %w[100], 'term-bg-l-black', 'sets bg color light black'], + [[], %w[101], 'term-bg-l-red', 'sets bg color light red'], + [[], %w[102], 'term-bg-l-green', 'sets bg color light green'], + [[], %w[103], 'term-bg-l-yellow', 'sets bg color light yellow'], + [[], %w[104], 'term-bg-l-blue', 'sets bg color light blue'], + [[], %w[105], 'term-bg-l-magenta', 'sets bg color light magenta'], + [[], %w[106], 'term-bg-l-cyan', 'sets bg color light cyan'], + [[], %w[107], 'term-bg-l-white', 'sets bg color light white'], + # reset + [%w[1], %w[0], '', 'resets style from format bold'], + [%w[1 3], %w[0], '', 'resets style from format bold and italic'], + [%w[1 3 term-fg-l-red term-bg-yellow], %w[0], '', 'resets all formats and colors'], + # misc + [[], %w[1 30 42 3], 'term-fg-l-black term-bg-green term-bold term-italic', 'adds fg color, bg color and formats from no style'], + [%w[3 31], %w[23 1 43], 'term-fg-l-red term-bg-yellow term-bold', 'replaces format italic with bold and adds a yellow background'] + ] + end + + with_them do + it 'change the style' do + style = described_class.new + style.update(initial_state) + + style.update(ansi_commands) + + expect(style.to_s).to eq(result) + end + end + end +end diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb new file mode 100644 index 00000000000..3c6bc46436b --- /dev/null +++ b/spec/lib/gitlab/ci/ansi2json_spec.rb @@ -0,0 +1,544 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Ansi2json do + subject { described_class } + + describe 'lines' do + it 'prints non-ansi as-is' do + expect(convert_json('Hello')).to eq([ + { offset: 0, content: [{ text: 'Hello' }] } + ]) + end + + it 'adds new line in a separate element' do + expect(convert_json("Hello\nworld")).to eq([ + { offset: 0, content: [{ text: 'Hello' }] }, + { offset: 6, content: [{ text: 'world' }] } + ]) + end + + it 'recognizes color changing ANSI sequences' do + expect(convert_json("\e[31mHello\e[0m")).to eq([ + { offset: 0, content: [{ text: 'Hello', style: 'term-fg-red' }] } + ]) + end + + it 'recognizes color changing ANSI sequences across multiple lines' do + expect(convert_json("\e[31mHello\nWorld\e[0m")).to eq([ + { offset: 0, content: [{ text: 'Hello', style: 'term-fg-red' }] }, + { offset: 11, content: [{ text: 'World', style: 'term-fg-red' }] } + ]) + end + + it 'recognizes background and foreground colors' do + expect(convert_json("\e[31;44mHello")).to eq([ + { offset: 0, content: [{ text: 'Hello', style: 'term-fg-red term-bg-blue' }] } + ]) + end + + it 'recognizes style changes within the same line' do + expect(convert_json("\e[31;44mHello\e[0m world")).to eq([ + { offset: 0, content: [ + { text: 'Hello', style: 'term-fg-red term-bg-blue' }, + { text: ' world' } + ] } + ]) + end + + context 'with section markers' do + let(:section_name) { 'prepare-script' } + let(:section_duration) { 63.seconds } + let(:section_start_time) { Time.new(2019, 9, 17).utc } + let(:section_end_time) { section_start_time + section_duration } + let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"} + let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"} + + it 'marks the first line of the section as header' do + expect(convert_json("Hello#{section_start}world!")).to eq([ + { + offset: 0, + content: [{ text: 'Hello' }] + }, + { + offset: 5, + content: [{ text: 'world!' }], + section: 'prepare-script', + section_header: true + } + ]) + end + + it 'does not marks the other lines of the section as header' do + expect(convert_json("outside section#{section_start}Hello\nworld!")).to eq([ + { + offset: 0, + content: [{ text: 'outside section' }] + }, + { + offset: 15, + content: [{ text: 'Hello' }], + section: 'prepare-script', + section_header: true + }, + { + offset: 65, + content: [{ text: 'world!' }], + section: 'prepare-script' + } + ]) + end + + it 'marks the last line of the section as footer' do + expect(convert_json("#{section_start}Good\nmorning\nworld!#{section_end}")).to eq([ + { + offset: 0, + content: [{ text: 'Good' }], + section: 'prepare-script', + section_header: true + }, + { + offset: 49, + content: [{ text: 'morning' }], + section: 'prepare-script' + }, + { + offset: 57, + content: [{ text: 'world!' }], + section: 'prepare-script' + }, + { + offset: 63, + content: [], + section_duration: '01:03', + section: 'prepare-script' + }, + { + offset: 63, + content: [] + } + ]) + end + + it 'marks the first line as header and footer if is the only line in the section' do + expect(convert_json("#{section_start}Hello world!#{section_end}")).to eq([ + { + offset: 0, + content: [{ text: 'Hello world!' }], + section: 'prepare-script', + section_header: true + }, + { + offset: 56, + content: [], + section: 'prepare-script', + section_duration: '01:03' + }, + { + offset: 56, + content: [] + } + ]) + end + + it 'does not add sections attribute to lines after the section is closed' do + expect(convert_json("#{section_start}Hello#{section_end}world")).to eq([ + { + offset: 0, + content: [{ text: 'Hello' }], + section: 'prepare-script', + section_header: true + }, + { + offset: 49, + content: [], + section: 'prepare-script', + section_duration: '01:03' + }, + { + offset: 49, + content: [{ text: 'world' }] + } + ]) + end + + it 'ignores section_end marker if no section_start exists' do + expect(convert_json("Hello #{section_end}world")).to eq([ + { + offset: 0, + content: [{ text: 'Hello world' }] + } + ]) + end + + context 'when section name contains .-_ and capital letters' do + let(:section_name) { 'a.Legit-SeCtIoN_namE' } + + it 'sanitizes the section name' do + expect(convert_json("Hello#{section_start}world!")).to eq([ + { + offset: 0, + content: [{ text: 'Hello' }] + }, + { + offset: 5, + content: [{ text: 'world!' }], + section: 'a-legit-section-name', + section_header: true + } + ]) + end + end + + context 'when section name includes $' do + let(:section_name) { 'my_$ection' } + + it 'ignores the section' do + expect(convert_json("#{section_start}hello")).to eq([ + { + offset: 0, + content: [{ text: "#{section_start.gsub("\033[0K", '')}hello" }] + } + ]) + end + end + + context 'when section name includes <' do + let(:section_name) { '<a_tag>' } + + it 'ignores the section' do + expect(convert_json("#{section_start}hello")).to eq([ + { + offset: 0, + content: [{ text: "#{section_start.gsub("\033[0K", '').gsub('<', '<')}hello" }] + } + ]) + end + end + + it 'prevents XSS injection' do + trace = "#{section_start}section_end:1:2<script>alert('XSS Hack!');</script>#{section_end}" + expect(convert_json(trace)).to eq([ + { + offset: 0, + content: [{ text: "section_end:1:2<script>alert('XSS Hack!');</script>" }], + section: 'prepare-script', + section_header: true + }, + { + offset: 95, + content: [], + section: 'prepare-script', + section_duration: '01:03' + }, + { + offset: 95, + content: [] + } + ]) + end + + context 'with nested section' do + let(:nested_section_name) { 'prepare-script-nested' } + let(:nested_section_duration) { 2.seconds } + let(:nested_section_start_time) { Time.new(2019, 9, 17).utc } + let(:nested_section_end_time) { nested_section_start_time + nested_section_duration } + let(:nested_section_start) { "section_start:#{nested_section_start_time.to_i}:#{nested_section_name}\r\033[0K"} + let(:nested_section_end) { "section_end:#{nested_section_end_time.to_i}:#{nested_section_name}\r\033[0K"} + + it 'adds multiple sections to the lines inside the nested section' do + trace = "Hello#{section_start}foo#{nested_section_start}bar#{nested_section_end}baz#{section_end}world" + + expect(convert_json(trace)).to eq([ + { + offset: 0, + content: [{ text: 'Hello' }] + }, + { + offset: 5, + content: [{ text: 'foo' }], + section: 'prepare-script', + section_header: true + }, + { + offset: 52, + content: [{ text: 'bar' }], + section: 'prepare-script-nested', + section_header: true + }, + { + offset: 106, + content: [], + section: 'prepare-script-nested', + section_duration: '00:02' + }, + { + offset: 106, + content: [{ text: 'baz' }], + section: 'prepare-script' + }, + { + offset: 158, + content: [], + section: 'prepare-script', + section_duration: '01:03' + }, + { + offset: 158, + content: [{ text: 'world' }] + } + ]) + end + + it 'adds multiple sections to the lines inside the nested section and closes all sections together' do + trace = "Hello#{section_start}\e[91mfoo\e[0m#{nested_section_start}bar#{nested_section_end}#{section_end}" + + expect(convert_json(trace)).to eq([ + { + offset: 0, + content: [{ text: 'Hello' }] + }, + { + offset: 5, + content: [{ text: 'foo', style: 'term-fg-l-red' }], + section: 'prepare-script', + section_header: true + }, + { + offset: 61, + content: [{ text: 'bar' }], + section: 'prepare-script-nested', + section_header: true + }, + { + offset: 115, + content: [], + section: 'prepare-script-nested', + section_duration: '00:02' + }, + { + offset: 115, + content: [], + section: 'prepare-script', + section_duration: '01:03' + }, + { + offset: 164, + content: [] + } + ]) + end + end + end + + describe 'incremental updates' do + let(:pass1_stream) { StringIO.new(pre_text) } + let(:pass2_stream) { StringIO.new(pre_text + text) } + let(:pass1) { subject.convert(pass1_stream) } + let(:pass2) { subject.convert(pass2_stream, pass1.state) } + + context 'with split word' do + let(:pre_text) { "\e[1mHello " } + let(:text) { "World" } + + let(:lines) do + [ + { offset: 0, content: [{ text: 'Hello World', style: 'term-bold' }] } + ] + end + + it 'returns the full line' do + expect(pass2.lines).to eq(lines) + expect(pass2.append).to be_falsey + end + end + + context 'with split word on second line' do + let(:pre_text) { "Good\nmorning " } + let(:text) { "World" } + + let(:lines) do + [ + { offset: 5, content: [{ text: 'morning World' }] } + ] + end + + it 'returns all lines since last partially processed line' do + expect(pass2.lines).to eq(lines) + expect(pass2.append).to be_truthy + end + end + + context 'with split sequence across multiple lines' do + let(:pre_text) { "\e[1mgood\nmorning\n" } + let(:text) { "\e[3mworld" } + + let(:lines) do + [ + { offset: 17, content: [{ text: 'world', style: 'term-bold term-italic' }] } + ] + end + + it 'returns the full line' do + expect(pass2.lines).to eq(lines) + expect(pass2.append).to be_truthy + end + end + + context 'with split partial sequence' do + let(:pre_text) { "hello\e" } + let(:text) { "[1m world" } + + let(:lines) do + [ + { offset: 0, content: [ + { text: 'hello' }, + { text: ' world', style: 'term-bold' } + ] } + ] + end + + it 'returns the full line' do + expect(pass2.lines).to eq(lines) + expect(pass2.append).to be_falsey + end + end + + context 'with split new line' do + let(:pre_text) { "hello\r" } + let(:text) { "\nworld" } + + let(:lines) do + [ + { offset: 0, content: [{ text: 'hello' }] }, + { offset: 7, content: [{ text: 'world' }] } + ] + end + + it 'returns the full line' do + expect(pass2.lines).to eq(lines) + expect(pass2.append).to be_falsey + end + end + + context 'with split section' do + let(:section_name) { 'prepare-script' } + let(:section_duration) { 63.seconds } + let(:section_start_time) { Time.new(2019, 9, 17).utc } + let(:section_end_time) { section_start_time + section_duration } + let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"} + let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"} + + context 'with split section body' do + let(:pre_text) { "#{section_start}this is a header\nand " } + let(:text) { "this\n is a body" } + + let(:lines) do + [ + { + offset: 61, + content: [{ text: 'and this' }], + section: 'prepare-script' + }, + { + offset: 70, + content: [{ text: ' is a body' }], + section: 'prepare-script' + } + ] + end + + it 'returns the full line' do + expect(pass2.lines).to eq(lines) + expect(pass2.append).to be_truthy + end + end + + context 'with split section where header is also split' do + let(:pre_text) { "#{section_start}this is " } + let(:text) { "a header\nand body" } + + let(:lines) do + [ + { + offset: 0, + content: [{ text: 'this is a header' }], + section: 'prepare-script', + section_header: true + }, + { + offset: 61, + content: [{ text: 'and body' }], + section: 'prepare-script' + } + ] + end + + it 'returns the full line' do + expect(pass2.lines).to eq(lines) + expect(pass2.append).to be_falsey + end + end + + context 'with split section end' do + let(:pre_text) { "#{section_start}this is a header\nthe" } + let(:text) { " body\nthe end#{section_end}" } + + let(:lines) do + [ + { + offset: 61, + content: [{ text: 'the body' }], + section: 'prepare-script' + }, + { + offset: 70, + content: [{ text: 'the end' }], + section: 'prepare-script' + }, + { + offset: 77, + content: [], + section: 'prepare-script', + section_duration: '01:03' + }, + { + offset: 77, + content: [] + } + ] + end + + it 'returns the full line' do + expect(pass2.lines).to eq(lines) + expect(pass2.append).to be_truthy + end + end + end + end + + describe 'trucates' do + let(:text) { "Hello World" } + let(:stream) { StringIO.new(text) } + let(:subject) { described_class.convert(stream) } + + before do + stream.seek(3, IO::SEEK_SET) + end + + it "returns truncated output" do + expect(subject.truncated).to be_truthy + end + + it "does not append output" do + expect(subject.append).to be_falsey + end + end + + def convert_json(data) + stream = StringIO.new(data) + subject.convert(stream).lines + end + end +end diff --git a/spec/lib/gitlab/diff/position_collection_spec.rb b/spec/lib/gitlab/diff/position_collection_spec.rb index de0e631ab03..f2a8312587c 100644 --- a/spec/lib/gitlab/diff/position_collection_spec.rb +++ b/spec/lib/gitlab/diff/position_collection_spec.rb @@ -35,14 +35,15 @@ describe Gitlab::Diff::PositionCollection do let(:text_position) { build_text_position } let(:folded_text_position) { build_text_position(old_line: 1, new_line: 1) } let(:image_position) { build_image_position } + let(:invalid_position) { 'a position' } let(:head_sha) { merge_request.diff_head_sha } let(:collection) do - described_class.new([text_position, folded_text_position, image_position], head_sha) + described_class.new([text_position, folded_text_position, image_position, invalid_position], head_sha) end describe '#to_a' do - it 'returns all positions' do + it 'returns all positions that are Gitlab::Diff::Position' do expect(collection.to_a).to eq([text_position, folded_text_position, image_position]) end end @@ -59,6 +60,14 @@ describe Gitlab::Diff::PositionCollection do expect(collection.unfoldable).to be_empty end end + + context 'when given head_sha is nil' do + let(:head_sha) { nil } + + it 'returns unfoldable diff positions unfiltered by head_sha' do + expect(collection.unfoldable).to eq([folded_text_position]) + end + end end describe '#concat' do diff --git a/spec/lib/gitlab/health_checks/probes/collection_spec.rb b/spec/lib/gitlab/health_checks/probes/collection_spec.rb new file mode 100644 index 00000000000..33efc640257 --- /dev/null +++ b/spec/lib/gitlab/health_checks/probes/collection_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::HealthChecks::Probes::Collection do + let(:readiness) { described_class.new(*checks) } + + describe '#call' do + subject { readiness.execute } + + context 'with all checks' do + let(:checks) do + [ + Gitlab::HealthChecks::DbCheck, + Gitlab::HealthChecks::Redis::RedisCheck, + Gitlab::HealthChecks::Redis::CacheCheck, + Gitlab::HealthChecks::Redis::QueuesCheck, + Gitlab::HealthChecks::Redis::SharedStateCheck, + Gitlab::HealthChecks::GitalyCheck + ] + end + + it 'responds with readiness checks data' do + expect(subject.http_status).to eq(200) + + expect(subject.json[:status]).to eq('ok') + expect(subject.json['db_check']).to contain_exactly(status: 'ok') + expect(subject.json['cache_check']).to contain_exactly(status: 'ok') + expect(subject.json['queues_check']).to contain_exactly(status: 'ok') + expect(subject.json['shared_state_check']).to contain_exactly(status: 'ok') + expect(subject.json['gitaly_check']).to contain_exactly( + status: 'ok', labels: { shard: 'default' }) + end + + context 'when Redis fails' do + before do + allow(Gitlab::HealthChecks::Redis::RedisCheck).to receive(:readiness).and_return( + Gitlab::HealthChecks::Result.new('redis_check', false, "check error")) + end + + it 'responds with failure' do + expect(subject.http_status).to eq(503) + + expect(subject.json[:status]).to eq('failed') + expect(subject.json['cache_check']).to contain_exactly(status: 'ok') + expect(subject.json['redis_check']).to contain_exactly( + status: 'failed', message: 'check error') + end + end + end + + context 'without checks' do + let(:checks) { [] } + + it 'responds with success' do + expect(subject.http_status).to eq(200) + + expect(subject.json).to eq(status: 'ok') + end + end + end +end diff --git a/spec/lib/gitlab/health_checks/probes/liveness_spec.rb b/spec/lib/gitlab/health_checks/probes/liveness_spec.rb deleted file mode 100644 index 91066cb8ba0..00000000000 --- a/spec/lib/gitlab/health_checks/probes/liveness_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::HealthChecks::Probes::Liveness do - let(:liveness) { described_class.new } - - describe '#call' do - subject { liveness.execute } - - it 'responds with liveness checks data' do - expect(subject.http_status).to eq(200) - - expect(subject.json[:status]).to eq('ok') - end - end -end diff --git a/spec/lib/gitlab/health_checks/probes/readiness_spec.rb b/spec/lib/gitlab/health_checks/probes/readiness_spec.rb deleted file mode 100644 index d88ffd984c2..00000000000 --- a/spec/lib/gitlab/health_checks/probes/readiness_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::HealthChecks::Probes::Readiness do - let(:readiness) { described_class.new } - - describe '#call' do - subject { readiness.execute } - - it 'responds with readiness checks data' do - expect(subject.http_status).to eq(200) - - expect(subject.json[:status]).to eq('ok') - expect(subject.json['db_check']).to contain_exactly(status: 'ok') - expect(subject.json['cache_check']).to contain_exactly(status: 'ok') - expect(subject.json['queues_check']).to contain_exactly(status: 'ok') - expect(subject.json['shared_state_check']).to contain_exactly(status: 'ok') - expect(subject.json['gitaly_check']).to contain_exactly( - status: 'ok', labels: { shard: 'default' }) - end - - context 'when Redis fails' do - before do - allow(Gitlab::HealthChecks::Redis::RedisCheck).to receive(:readiness).and_return( - Gitlab::HealthChecks::Result.new('redis_check', false, "check error")) - end - - it 'responds with failure' do - expect(subject.http_status).to eq(503) - - expect(subject.json[:status]).to eq('failed') - expect(subject.json['cache_check']).to contain_exactly(status: 'ok') - expect(subject.json['redis_check']).to contain_exactly( - status: 'failed', message: 'check error') - end - end - end -end diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index c1d171815ba..6bf837f1d3f 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -146,7 +146,7 @@ describe Gitlab do describe '.ee?' do before do - stub_env('IS_GITLAB_EE', nil) # Make sure the ENV is clean + stub_env('FOSS_ONLY', nil) # Make sure the ENV is clean described_class.instance_variable_set(:@is_ee, nil) end @@ -154,42 +154,66 @@ describe Gitlab do described_class.instance_variable_set(:@is_ee, nil) end - it 'returns true when using Enterprise Edition' do - root = Pathname.new('dummy') - license_path = double(:path, exist?: true) + context 'for EE' do + before do + root = Pathname.new('dummy') + license_path = double(:path, exist?: true) - allow(described_class) - .to receive(:root) - .and_return(root) + allow(described_class) + .to receive(:root) + .and_return(root) - allow(root) - .to receive(:join) - .with('ee/app/models/license.rb') - .and_return(license_path) + allow(root) + .to receive(:join) + .with('ee/app/models/license.rb') + .and_return(license_path) + end - expect(described_class.ee?).to eq(true) - end + context 'when using FOSS_ONLY=1' do + before do + stub_env('FOSS_ONLY', '1') + end - it 'returns false when using Community Edition' do - root = double(:path) - license_path = double(:path, exists?: false) + it 'returns not to be EE' do + expect(described_class).not_to be_ee + end + end - allow(described_class) - .to receive(:root) - .and_return(Pathname.new('dummy')) + context 'when using FOSS_ONLY=0' do + before do + stub_env('FOSS_ONLY', '0') + end - allow(root) - .to receive(:join) - .with('ee/app/models/license.rb') - .and_return(license_path) + it 'returns to be EE' do + expect(described_class).to be_ee + end + end - expect(described_class.ee?).to eq(false) + context 'when using default FOSS_ONLY' do + it 'returns to be EE' do + expect(described_class).to be_ee + end + end end - it 'returns true when the IS_GITLAB_EE variable is not empty' do - stub_env('IS_GITLAB_EE', '1') + context 'for CE' do + before do + root = double(:path) + license_path = double(:path, exists?: false) - expect(described_class.ee?).to eq(true) + allow(described_class) + .to receive(:root) + .and_return(Pathname.new('dummy')) + + allow(root) + .to receive(:join) + .with('ee/app/models/license.rb') + .and_return(license_path) + end + + it 'returns not to be EE' do + expect(described_class).not_to be_ee + end end end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 84c25b93fc6..702a6fab0e6 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -56,6 +56,10 @@ describe ApplicationSetting do it { is_expected.not_to allow_value(nil).for(:protected_paths) } it { is_expected.to allow_value([]).for(:protected_paths) } + it { is_expected.to allow_value(3).for(:push_event_hooks_limit) } + it { is_expected.not_to allow_value('three').for(:push_event_hooks_limit) } + it { is_expected.not_to allow_value(nil).for(:push_event_hooks_limit) } + context "when user accepted let's encrypt terms of service" do before do setting.update(lets_encrypt_terms_of_service_accepted: true) diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 51ed8e9421b..3a0b3c46ad0 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -348,4 +348,17 @@ describe Deployment do expect(deployment.deployed_by).to eq(build_user) end end + + describe '.find_successful_deployment!' do + it 'returns a successful deployment' do + deploy = create(:deployment, :success) + + expect(described_class.find_successful_deployment!(deploy.iid)).to eq(deploy) + end + + it 'raises when no deployment is found' do + expect { described_class.find_successful_deployment!(-1) } + .to raise_error(ActiveRecord::RecordNotFound) + end + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 521c4704c87..786f3b832c4 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -882,4 +882,19 @@ describe Environment, :use_clean_rails_memory_store_caching do end end end + + describe '.find_or_create_by_name' do + it 'finds an existing environment if it exists' do + env = create(:environment) + + expect(described_class.find_or_create_by_name(env.name)).to eq(env) + end + + it 'creates an environment if it does not exist' do + env = project.environments.find_or_create_by_name('kittens') + + expect(env).to be_an_instance_of(described_class) + expect(env).to be_persisted + end + end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 6093464c949..e61a064e82c 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -40,14 +40,14 @@ describe ProjectPolicy do update_commit_status create_build update_build create_pipeline update_pipeline create_merge_request_from create_wiki push_code resolve_note create_container_image update_container_image destroy_container_image - create_environment create_deployment create_release update_release + create_environment create_deployment update_deployment create_release update_release ] end let(:base_maintainer_permissions) do %i[ push_to_delete_protected_branch update_project_snippet update_environment - update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project + admin_project_snippet admin_project_member admin_note admin_wiki admin_project admin_commit_status admin_build admin_container_image admin_pipeline admin_environment admin_deployment destroy_release add_cluster daily_statistics diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index 3dac7225b7a..ad7be531979 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe API::Deployments do @@ -96,4 +98,164 @@ describe API::Deployments do end end end + + describe 'POST /projects/:id/deployments' do + let!(:project) { create(:project, :repository) } + let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' } + + context 'as a maintainer' do + it 'creates a new deployment' do + post( + api("/projects/#{project.id}/deployments", user), + params: { + environment: 'production', + sha: sha, + ref: 'master', + tag: false, + status: 'success' + } + ) + + expect(response).to have_gitlab_http_status(201) + + expect(json_response['sha']).to eq(sha) + expect(json_response['ref']).to eq('master') + expect(json_response['environment']['name']).to eq('production') + end + + it 'errors when creating a deployment with an invalid name' do + post( + api("/projects/#{project.id}/deployments", user), + params: { + environment: 'a' * 300, + sha: sha, + ref: 'master', + tag: false, + status: 'success' + } + ) + + expect(response).to have_gitlab_http_status(500) + end + end + + context 'as a developer' do + it 'creates a new deployment' do + developer = create(:user) + + project.add_developer(developer) + + post( + api("/projects/#{project.id}/deployments", developer), + params: { + environment: 'production', + sha: sha, + ref: 'master', + tag: false, + status: 'success' + } + ) + + expect(response).to have_gitlab_http_status(201) + + expect(json_response['sha']).to eq(sha) + expect(json_response['ref']).to eq('master') + end + end + + context 'as non member' do + it 'returns a 404 status code' do + post( + api( "/projects/#{project.id}/deployments", non_member), + params: { + environment: 'production', + sha: '123', + ref: 'master', + tag: false, + status: 'success' + } + ) + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe 'PUT /projects/:id/deployments/:deployment_id' do + let(:project) { create(:project) } + let(:build) { create(:ci_build, :failed, project: project) } + let(:environment) { create(:environment, project: project) } + let(:deploy) do + create( + :deployment, + :failed, + project: project, + environment: environment, + deployable: nil + ) + end + + context 'as a maintainer' do + it 'returns a 403 when updating a deployment with a build' do + deploy.update(deployable: build) + + put( + api("/projects/#{project.id}/deployments/#{deploy.id}", user), + params: { status: 'success' } + ) + + expect(response).to have_gitlab_http_status(403) + end + + it 'updates a deployment without an associated build' do + put( + api("/projects/#{project.id}/deployments/#{deploy.id}", user), + params: { status: 'success' } + ) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['status']).to eq('success') + end + end + + context 'as a developer' do + let(:developer) { create(:user) } + + before do + project.add_developer(developer) + end + + it 'returns a 403 when updating a deployment with a build' do + deploy.update(deployable: build) + + put( + api("/projects/#{project.id}/deployments/#{deploy.id}", developer), + params: { status: 'success' } + ) + + expect(response).to have_gitlab_http_status(403) + end + + it 'updates a deployment without an associated build' do + put( + api("/projects/#{project.id}/deployments/#{deploy.id}", developer), + params: { status: 'success' } + ) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['status']).to eq('success') + end + end + + context 'as non member' do + it 'returns a 404 status code' do + put( + api("/projects/#{project.id}/deployments/#{deploy.id}", non_member), + params: { status: 'success' } + ) + + expect(response).to have_gitlab_http_status(404) + end + end + end end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 7e67ee28bef..eb55d747179 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -87,6 +87,15 @@ describe API::Members do expect(json_response.first['username']).to eq(maintainer.username) end + it 'finds members with the given user_ids' do + get api(members_url, developer), params: { user_ids: [maintainer.id, developer.id, stranger.id] } + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |u| u['id'] }).to contain_exactly(maintainer.id, developer.id) + end + it 'finds all members with no query specified' do get api(members_url, developer), params: { query: '' } @@ -155,10 +164,10 @@ describe API::Members do end end - shared_examples 'GET /:source_type/:id/members/:user_id' do |source_type| - context "with :source_type == #{source_type.pluralize}" do + shared_examples 'GET /:source_type/:id/members/(all/):user_id' do |source_type, all| + context "with :source_type == #{source_type.pluralize} and all == #{all}" do it_behaves_like 'a 404 response when source is private' do - let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) } + let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members/#{all ? 'all/' : ''}#{developer.id}", stranger) } end context 'when authenticated as a non-member' do @@ -166,7 +175,7 @@ describe API::Members do context "as a #{type}" do it 'returns 200' do user = public_send(type) - get api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user) + get api("/#{source_type.pluralize}/#{source.id}/members/#{all ? 'all/' : ''}#{developer.id}", user) expect(response).to have_gitlab_http_status(200) # User attributes @@ -434,12 +443,14 @@ describe API::Members do end end - it_behaves_like 'GET /:source_type/:id/members/:user_id', 'project' do - let(:source) { project } - end + [false, true].each do |all| + it_behaves_like 'GET /:source_type/:id/members/(all/):user_id', 'project', all do + let(:source) { all ? create(:project, :public, group: group) : project } + end - it_behaves_like 'GET /:source_type/:id/members/:user_id', 'group' do - let(:source) { group } + it_behaves_like 'GET /:source_type/:id/members/(all/):user_id', 'group', all do + let(:source) { all ? create(:group, parent: group) : group } + end end it_behaves_like 'POST /:source_type/:id/members', 'project' do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index d98b9be726a..af1cf80e9d3 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -72,7 +72,8 @@ describe API::Settings, 'Settings' do default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE, local_markdown_version: 3, allow_local_requests_from_web_hooks_and_services: true, - allow_local_requests_from_system_hooks: false + allow_local_requests_from_system_hooks: false, + push_event_hooks_limit: 2 } expect(response).to have_gitlab_http_status(200) @@ -102,6 +103,7 @@ describe API::Settings, 'Settings' do expect(json_response['local_markdown_version']).to eq(3) expect(json_response['allow_local_requests_from_web_hooks_and_services']).to eq(true) expect(json_response['allow_local_requests_from_system_hooks']).to eq(false) + expect(json_response['push_event_hooks_limit']).to eq(2) end end diff --git a/spec/services/update_deployment_service_spec.rb b/spec/services/deployments/after_create_service_spec.rb index 343dab8a974..b34483ea85b 100644 --- a/spec/services/update_deployment_service_spec.rb +++ b/spec/services/deployments/after_create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe UpdateDeploymentService do +describe Deployments::AfterCreateService do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:options) { { name: 'production' } } diff --git a/spec/services/deployments/create_service_spec.rb b/spec/services/deployments/create_service_spec.rb new file mode 100644 index 00000000000..e41c8259ea9 --- /dev/null +++ b/spec/services/deployments/create_service_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Deployments::CreateService do + let(:environment) do + double( + :environment, + deployment_platform: double(:platform, cluster_id: 1), + project_id: 2, + id: 3 + ) + end + + let(:user) { double(:user) } + + describe '#execute' do + let(:service) { described_class.new(environment, user, {}) } + + it 'does not run the AfterCreateService service if the deployment is not persisted' do + deploy = double(:deployment, persisted?: false) + + expect(service) + .to receive(:create_deployment) + .and_return(deploy) + + expect(Deployments::AfterCreateService) + .not_to receive(:new) + + expect(service.execute).to eq(deploy) + end + + it 'runs the AfterCreateService service if the deployment is persisted' do + deploy = double(:deployment, persisted?: true) + after_service = double(:after_create_service) + + expect(service) + .to receive(:create_deployment) + .and_return(deploy) + + expect(Deployments::AfterCreateService) + .to receive(:new) + .with(deploy) + .and_return(after_service) + + expect(after_service) + .to receive(:execute) + + expect(service.execute).to eq(deploy) + end + end + + describe '#create_deployment' do + it 'creates a deployment' do + environment = build(:environment) + service = described_class.new(environment, user, {}) + + expect(environment.deployments) + .to receive(:create) + .with(an_instance_of(Hash)) + + service.create_deployment + end + end + + describe '#deployment_attributes' do + it 'only includes attributes that we want to persist' do + service = described_class.new( + environment, + user, + ref: 'master', + tag: true, + sha: '123', + foo: 'bar', + on_stop: 'stop', + status: 'running' + ) + + expect(service.deployment_attributes).to eq( + cluster_id: 1, + project_id: 2, + environment_id: 3, + ref: 'master', + tag: true, + sha: '123', + user: user, + on_stop: 'stop', + status: 'running' + ) + end + end +end diff --git a/spec/services/deployments/update_service_spec.rb b/spec/services/deployments/update_service_spec.rb new file mode 100644 index 00000000000..a923099b82c --- /dev/null +++ b/spec/services/deployments/update_service_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Deployments::UpdateService do + let(:deploy) { create(:deployment, :running) } + let(:service) { described_class.new(deploy, status: 'success') } + + describe '#execute' do + it 'updates the status of a deployment' do + expect(service.execute).to eq(true) + expect(deploy.status).to eq('success') + end + end +end diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb index e71900e3c0d..90b3eb38469 100644 --- a/spec/services/git/base_hooks_service_spec.rb +++ b/spec/services/git/base_hooks_service_spec.rb @@ -8,7 +8,6 @@ describe Git::BaseHooksService do let(:user) { create(:user) } let(:project) { create(:project, :repository) } - let(:oldrev) { Gitlab::Git::BLANK_SHA } let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0 let(:ref) { 'refs/tags/v1.1.0' } @@ -26,7 +25,17 @@ describe Git::BaseHooksService do let(:project) { create(:project, :repository) } - subject { TestService.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }) } + let(:params) do + { + change: { + oldrev: oldrev, + newrev: newrev, + ref: ref + } + } + end + + subject { TestService.new(project, user, params) } context '#execute_hooks' do before do @@ -83,5 +92,21 @@ describe Git::BaseHooksService do end end end + + context 'execute_project_hooks param set to false' do + before do + params[:execute_project_hooks] = false + + allow(project).to receive(:has_active_hooks?).and_return(true) + allow(project).to receive(:has_active_services?).and_return(true) + end + + it 'does not execute hooks and services' do + expect(project).not_to receive(:execute_hooks) + expect(project).not_to receive(:execute_services) + + subject.execute + end + end end end diff --git a/spec/services/git/process_ref_changes_service_spec.rb b/spec/services/git/process_ref_changes_service_spec.rb index 4d394a29867..eeb395f6c7b 100644 --- a/spec/services/git/process_ref_changes_service_spec.rb +++ b/spec/services/git/process_ref_changes_service_spec.rb @@ -28,12 +28,66 @@ describe Git::ProcessRefChangesService do it "calls #{push_service_class}" do expect(push_service_class) .to receive(:new) + .with(project, project.owner, hash_including(execute_project_hooks: true)) .exactly(changes.count).times .and_return(service) subject.execute end + context 'changes exceed push_event_hooks_limit' do + def multiple_changes(change, count) + Array.new(count).map.with_index do |n, index| + { index: index, oldrev: change[:oldrev], newrev: change[:newrev], ref: "#{change[:ref]}#{n}" } + end + end + + let(:push_event_hooks_limit) { 3 } + + let(:changes) do + multiple_changes( + { oldrev: '123456', newrev: '789012', ref: "#{ref_prefix}/test" }, + push_event_hooks_limit + 1 + ) + end + + before do + stub_application_setting(push_event_hooks_limit: push_event_hooks_limit) + end + + context 'git_push_execute_all_project_hooks is disabled' do + before do + stub_feature_flags(git_push_execute_all_project_hooks: false) + end + + it "calls #{push_service_class} with execute_project_hooks set to false" do + expect(push_service_class) + .to receive(:new) + .with(project, project.owner, hash_including(execute_project_hooks: false)) + .exactly(changes.count).times + .and_return(service) + + subject.execute + end + end + + context 'git_push_execute_all_project_hooks is enabled' do + before do + stub_feature_flags(git_push_execute_all_project_hooks: true) + end + + it "calls #{push_service_class} with execute_project_hooks set to true" do + expect(push_service_class) + .to receive(:new) + .with(project, project.owner, hash_including(execute_project_hooks: true)) + .exactly(changes.count).times + .and_return(service) + + subject.execute + end + end + end + context 'pipeline creation' do context 'with valid .gitlab-ci.yml' do before do diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb index c97eeba87db..bbe793a81bc 100644 --- a/spec/support/features/rss_shared_examples.rb +++ b/spec/support/features/rss_shared_examples.rb @@ -8,7 +8,9 @@ end shared_examples "it has an RSS button with current_user's feed token" do it "shows the RSS button with current_user's feed token" do - expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']") + expect(page) + .to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']") + .or have_css("a.js-rss-button[href*='feed_token=#{user.feed_token}']") end end @@ -20,6 +22,8 @@ end shared_examples "it has an RSS button without a feed token" do it "shows the RSS button without a feed token" do - expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])") + expect(page) + .to have_css("a:has(.fa-rss):not([href*='feed_token'])") + .or have_css("a.js-rss-button:not([href*='feed_token'])") end end diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb index 1aa40dcde3d..65398c13d90 100644 --- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -38,14 +38,14 @@ RSpec.shared_context 'ProjectPolicy context' do update_commit_status create_build update_build create_pipeline update_pipeline create_merge_request_from create_wiki push_code resolve_note create_container_image update_container_image - create_environment create_deployment create_release update_release + create_environment create_deployment update_deployment create_release update_release ] end let(:base_maintainer_permissions) do %i[ push_to_delete_protected_branch update_project_snippet update_environment - update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project + admin_project_snippet admin_project_member admin_note admin_wiki admin_project admin_commit_status admin_build admin_container_image admin_pipeline admin_environment admin_deployment destroy_release add_cluster daily_statistics diff --git a/spec/workers/deployments/success_worker_spec.rb b/spec/workers/deployments/success_worker_spec.rb index 1c68922b03d..7f2816d7535 100644 --- a/spec/workers/deployments/success_worker_spec.rb +++ b/spec/workers/deployments/success_worker_spec.rb @@ -8,8 +8,8 @@ describe Deployments::SuccessWorker do context 'when successful deployment' do let(:deployment) { create(:deployment, :success) } - it 'executes UpdateDeploymentService' do - expect(UpdateDeploymentService) + it 'executes Deployments::AfterCreateService' do + expect(Deployments::AfterCreateService) .to receive(:new).with(deployment).and_call_original subject @@ -19,8 +19,8 @@ describe Deployments::SuccessWorker do context 'when canceled deployment' do let(:deployment) { create(:deployment, :canceled) } - it 'does not execute UpdateDeploymentService' do - expect(UpdateDeploymentService).not_to receive(:new) + it 'does not execute Deployments::AfterCreateService' do + expect(Deployments::AfterCreateService).not_to receive(:new) subject end @@ -29,8 +29,8 @@ describe Deployments::SuccessWorker do context 'when deploy record does not exist' do let(:deployment) { nil } - it 'does not execute UpdateDeploymentService' do - expect(UpdateDeploymentService).not_to receive(:new) + it 'does not execute Deployments::AfterCreateService' do + expect(Deployments::AfterCreateService).not_to receive(:new) subject end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 6983fea021c..34aaa9bb1e9 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -93,6 +93,8 @@ describe PostReceive do end context 'with changes' do + let(:push_service) { double(execute: true) } + before do allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner) allow(Gitlab::GlRepository).to receive(:parse).and_return([project, Gitlab::GlRepository::PROJECT]) |