Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-05 15:07:43 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-05 15:07:43 +0300
commit872319738757edc0483346c75a2407f7019b963f (patch)
treed5953edec6184dda1f53c5994c3ebcebc9e815a2 /spec
parent8f764d21b0011056e1492d92afe3bd40b847b9f7 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb20
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb5
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb36
-rw-r--r--spec/db/schema_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb2
-rw-r--r--spec/frontend/issuables_list/components/issuable_spec.js1
-rw-r--r--spec/frontend/issuables_list/components/issuables_list_app_spec.js1
-rw-r--r--spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap33
-rw-r--r--spec/frontend/registry/settings/components/registry_settings_app_spec.js40
-rw-r--r--spec/frontend/registry/settings/stores/actions_spec.js20
-rw-r--r--spec/frontend/registry/settings/stores/mutations_spec.js21
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js32
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_spec.js194
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js118
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js313
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_stop_button_spec.js95
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/review_app_link_spec.js1
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js9
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js7
-rw-r--r--spec/lib/api/entities/release_spec.rb40
-rw-r--r--spec/models/ci/build_spec.rb50
-rw-r--r--spec/services/cohorts_service_spec.rb24
23 files changed, 715 insertions, 350 deletions
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 349d73f13ca..ac13c0f2d9e 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -154,7 +154,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
.and_return(merge_request)
end
- it 'does not serialize builds in exposed stages', :sidekiq_might_not_need_inline do
+ it 'does not serialize builds in exposed stages' do
get_show_json
json_response.dig('pipeline', 'details', 'stages').tap do |stages|
@@ -183,7 +183,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'job is cancelable' do
let(:job) { create(:ci_build, :running, pipeline: pipeline) }
- it 'cancel_path is present with correct redirect', :sidekiq_might_not_need_inline do
+ it 'cancel_path is present with correct redirect' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['cancel_path']).to include(CGI.escape(json_response['build_path']))
@@ -193,7 +193,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'with web terminal' do
let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) }
- it 'exposes the terminal path', :sidekiq_might_not_need_inline do
+ it 'exposes the terminal path' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['terminal_path']).to match(%r{/terminal})
@@ -268,7 +268,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project.add_maintainer(user) # Need to be a maintianer to view cluster.path
end
- it 'exposes the deployment information', :sidekiq_might_not_need_inline do
+ it 'exposes the deployment information' do
get_show_json
expect(response).to have_gitlab_http_status(:ok)
@@ -292,7 +292,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
sign_in(user)
end
- it 'user can edit runner', :sidekiq_might_not_need_inline do
+ it 'user can edit runner' do
get_show_json
expect(response).to have_gitlab_http_status(:ok)
@@ -312,7 +312,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
sign_in(user)
end
- it 'user can not edit runner', :sidekiq_might_not_need_inline do
+ it 'user can not edit runner' do
get_show_json
expect(response).to have_gitlab_http_status(:ok)
@@ -331,7 +331,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
sign_in(user)
end
- it 'user can not edit runner', :sidekiq_might_not_need_inline do
+ it 'user can not edit runner' do
get_show_json
expect(response).to have_gitlab_http_status(:ok)
@@ -412,7 +412,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when job has trace' do
let(:job) { create(:ci_build, :running, :trace_live, pipeline: pipeline) }
- it "has_trace is true", :sidekiq_might_not_need_inline do
+ it "has_trace is true" do
get_show_json
expect(response).to match_response_schema('job/job_details')
@@ -458,7 +458,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
end
- context 'user is a maintainer', :sidekiq_might_not_need_inline do
+ context 'user is a maintainer' do
before do
project.add_maintainer(user)
@@ -512,7 +512,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
def get_show_json
expect { get_show(id: job.id, format: :json) }
- .not_to change { Gitlab::GitalyClient.get_request_count }
+ .to change { Gitlab::GitalyClient.get_request_count }.by(1) # ListCommitsByOid
end
def get_show(**extra_params)
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 3c7f69f0e6e..b4549e4e635 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -93,7 +93,7 @@ describe Projects::PipelinesController do
end
context 'when performing gitaly calls', :request_store do
- it 'limits the Gitaly requests', :sidekiq_might_not_need_inline do
+ it 'limits the Gitaly requests' do
# Isolate from test preparation (Repository#exists? is also cached in RequestStore)
RequestStore.end!
RequestStore.clear!
@@ -101,8 +101,9 @@ describe Projects::PipelinesController do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ # ListCommitsByOid, RepositoryExists, HasLocalBranches
expect { get_pipelines_index_json }
- .to change { Gitlab::GitalyClient.get_request_count }.by(2)
+ .to change { Gitlab::GitalyClient.get_request_count }.by(3)
end
end
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 5b528c3be52..6592d8d5702 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -184,19 +184,39 @@ describe Projects::ReleasesController do
sign_in(user)
end
- it 'returns the correct evidence summary as a json' do
- subject
+ context 'when the user is a developer' do
+ it 'returns the correct evidence summary as a json' do
+ subject
+
+ expect(json_response).to eq(release.evidence.summary)
+ end
- expect(json_response).to eq(release.evidence.summary)
+ context 'when the release was created before evidence existed' do
+ before do
+ release.evidence.destroy
+ end
+
+ it 'returns an empty json' do
+ subject
+
+ expect(json_response).to eq({})
+ end
+ end
end
- context 'when the release was created before evidence existed' do
- it 'returns an empty json' do
- release.evidence.destroy
+ context 'when the user is a guest for the project' do
+ before do
+ project.add_guest(user)
+ end
- subject
+ context 'when the project is private' do
+ let(:project) { private_project }
+
+ it_behaves_like 'not found'
+ end
- expect(json_response).to eq({})
+ context 'when the project is public' do
+ it_behaves_like 'successful request'
end
end
end
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index d4fab48b426..a42916a83a6 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -43,6 +43,7 @@ describe 'Database schema' do
geo_nodes: %w[oauth_application_id],
geo_repository_deleted_events: %w[project_id],
geo_upload_deleted_events: %w[upload_id model_id],
+ gitlab_subscription_histories: %w[gitlab_subscription_id hosted_plan_id namespace_id],
import_failures: %w[project_id],
identities: %w[user_id],
issues: %w[last_edited_by_id state_id],
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index cdffd2ae2f6..3743ef0f25d 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -96,7 +96,7 @@ describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request)
wait_for_requests
- expect(page).to have_content("Failed to deploy to #{environment.name}")
+ expect(page).to have_content("Canceled deploy to #{environment.name}")
expect(page).not_to have_css('.js-deploy-time')
end
end
diff --git a/spec/frontend/issuables_list/components/issuable_spec.js b/spec/frontend/issuables_list/components/issuable_spec.js
index 6148f3c68f2..b6851a0e24c 100644
--- a/spec/frontend/issuables_list/components/issuable_spec.js
+++ b/spec/frontend/issuables_list/components/issuable_spec.js
@@ -45,6 +45,7 @@ describe('Issuable component', () => {
...props,
},
sync: false,
+ attachToDocument: true,
});
};
diff --git a/spec/frontend/issuables_list/components/issuables_list_app_spec.js b/spec/frontend/issuables_list/components/issuables_list_app_spec.js
index e598a9c5a5d..dad4e74cb40 100644
--- a/spec/frontend/issuables_list/components/issuables_list_app_spec.js
+++ b/spec/frontend/issuables_list/components/issuables_list_app_spec.js
@@ -49,6 +49,7 @@ describe('Issuables list component', () => {
},
localVue,
sync: false,
+ attachToDocument: true,
});
};
diff --git a/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap b/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap
new file mode 100644
index 00000000000..c6dbb1da8e9
--- /dev/null
+++ b/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap
@@ -0,0 +1,33 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Registry List renders 1`] = `
+<div>
+ <p>
+
+ Tag retention policies are designed to:
+
+ </p>
+
+ <ul>
+ <li>
+ Keep and protect the images that matter most.
+ </li>
+
+ <li>
+
+ Automatically remove extra images that aren't designed to be kept.
+
+ </li>
+ </ul>
+
+ <p>
+ Read more about the
+ <a
+ href="foo"
+ target="_blank"
+ >
+ Container Registry tag retention policies
+ </a>
+ </p>
+</div>
+`;
diff --git a/spec/frontend/registry/settings/components/registry_settings_app_spec.js b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
new file mode 100644
index 00000000000..666d970aa6b
--- /dev/null
+++ b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
@@ -0,0 +1,40 @@
+import Vuex from 'vuex';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import component from '~/registry/settings/components/registry_settings_app.vue';
+import { createStore } from '~/registry/settings/stores/';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Registry List', () => {
+ let wrapper;
+ let store;
+
+ const helpPagePath = 'foo';
+ const findHelpLink = () => wrapper.find({ ref: 'help-link' }).find('a');
+
+ const mountComponent = (options = {}) =>
+ shallowMount(component, {
+ sync: false,
+ store,
+ ...options,
+ });
+
+ beforeEach(() => {
+ store = createStore();
+ store.dispatch('setInitialState', { helpPagePath });
+ wrapper = mountComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders an help link dependant on the helphPagePath', () => {
+ expect(findHelpLink().attributes('href')).toBe(helpPagePath);
+ });
+});
diff --git a/spec/frontend/registry/settings/stores/actions_spec.js b/spec/frontend/registry/settings/stores/actions_spec.js
new file mode 100644
index 00000000000..205c3a6ee21
--- /dev/null
+++ b/spec/frontend/registry/settings/stores/actions_spec.js
@@ -0,0 +1,20 @@
+import * as actions from '~/registry/settings/stores/actions';
+import * as types from '~/registry/settings/stores/mutation_types';
+import testAction from 'helpers/vuex_action_helper';
+
+jest.mock('~/flash.js');
+
+describe('Actions Registry Store', () => {
+ describe('setInitialState', () => {
+ it('should set the initial state', done => {
+ testAction(
+ actions.setInitialState,
+ 'foo',
+ {},
+ [{ type: types.SET_INITIAL_STATE, payload: 'foo' }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/registry/settings/stores/mutations_spec.js b/spec/frontend/registry/settings/stores/mutations_spec.js
new file mode 100644
index 00000000000..421cd3f13cb
--- /dev/null
+++ b/spec/frontend/registry/settings/stores/mutations_spec.js
@@ -0,0 +1,21 @@
+import mutations from '~/registry/settings/stores/mutations';
+import * as types from '~/registry/settings/stores/mutation_types';
+import createState from '~/registry/settings/stores/state';
+
+describe('Mutations Registry Store', () => {
+ let mockState;
+
+ beforeEach(() => {
+ mockState = createState();
+ });
+
+ describe('SET_INITIAL_STATE', () => {
+ it('should set the initial state', () => {
+ const payload = { helpPagePath: 'foo', registrySettingsEndpoint: 'bar' };
+ const expectedState = { ...mockState, ...payload };
+ mutations[types.SET_INITIAL_STATE](mockState, payload);
+
+ expect(mockState.endpoint).toEqual(expectedState.endpoint);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js b/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js
new file mode 100644
index 00000000000..f8f4cb627dd
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js
@@ -0,0 +1,32 @@
+import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
+
+const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/review-apps/environments/15',
+ stop_url: '/root/review-apps/environments/15/stop',
+ metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
+ external_url: 'http://gitlab.com.',
+ external_url_formatted: 'gitlab',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ details: {},
+ status: SUCCESS,
+ changes: [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
+};
+
+export default deploymentMockData;
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
new file mode 100644
index 00000000000..78e086e473d
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_spec.js
@@ -0,0 +1,194 @@
+import { mount } from '@vue/test-utils';
+import DeploymentComponent from '~/vue_merge_request_widget/components/deployment/deployment.vue';
+import DeploymentInfo from '~/vue_merge_request_widget/components/deployment/deployment_info.vue';
+import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
+import DeploymentStopButton from '~/vue_merge_request_widget/components/deployment/deployment_stop_button.vue';
+import {
+ CREATED,
+ RUNNING,
+ SUCCESS,
+ FAILED,
+ CANCELED,
+} from '~/vue_merge_request_widget/components/deployment/constants';
+import deploymentMockData from './deployment_mock_data';
+
+const deployDetail = {
+ playable_build: {
+ retry_path: '/root/test-deployments/-/jobs/1131/retry',
+ play_path: '/root/test-deployments/-/jobs/1131/play',
+ },
+ isManual: true,
+};
+
+describe('Deployment component', () => {
+ let wrapper;
+
+ const factory = (options = {}) => {
+ // This destroys any wrappers created before a nested call to factory reassigns it
+ if (wrapper && wrapper.destroy) {
+ wrapper.destroy();
+ }
+ wrapper = mount(DeploymentComponent, {
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ factory({
+ propsData: {
+ deployment: deploymentMockData,
+ showMetrics: false,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('always renders DeploymentInfo', () => {
+ expect(wrapper.find(DeploymentInfo).exists()).toBe(true);
+ });
+
+ describe('status message and buttons', () => {
+ const noActions = [];
+ const noDetails = { isManual: false };
+ const deployGroup = [DeploymentViewButton, DeploymentStopButton];
+
+ describe.each`
+ status | previous | deploymentDetails | text | actionButtons
+ ${CREATED} | ${true} | ${deployDetail} | ${'Can deploy manually to'} | ${deployGroup}
+ ${CREATED} | ${true} | ${noDetails} | ${'Will deploy to'} | ${deployGroup}
+ ${CREATED} | ${false} | ${deployDetail} | ${'Can deploy manually to'} | ${noActions}
+ ${CREATED} | ${false} | ${noDetails} | ${'Will deploy to'} | ${noActions}
+ ${RUNNING} | ${true} | ${deployDetail} | ${'Deploying to'} | ${deployGroup}
+ ${RUNNING} | ${true} | ${noDetails} | ${'Deploying to'} | ${deployGroup}
+ ${RUNNING} | ${false} | ${deployDetail} | ${'Deploying to'} | ${noActions}
+ ${RUNNING} | ${false} | ${noDetails} | ${'Deploying to'} | ${noActions}
+ ${SUCCESS} | ${true} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
+ ${SUCCESS} | ${true} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
+ ${SUCCESS} | ${false} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
+ ${SUCCESS} | ${false} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
+ ${FAILED} | ${true} | ${deployDetail} | ${'Failed to deploy to'} | ${deployGroup}
+ ${FAILED} | ${true} | ${noDetails} | ${'Failed to deploy to'} | ${deployGroup}
+ ${FAILED} | ${false} | ${deployDetail} | ${'Failed to deploy to'} | ${noActions}
+ ${FAILED} | ${false} | ${noDetails} | ${'Failed to deploy to'} | ${noActions}
+ ${CANCELED} | ${true} | ${deployDetail} | ${'Canceled deploy to'} | ${deployGroup}
+ ${CANCELED} | ${true} | ${noDetails} | ${'Canceled deploy to'} | ${deployGroup}
+ ${CANCELED} | ${false} | ${deployDetail} | ${'Canceled deploy to'} | ${noActions}
+ ${CANCELED} | ${false} | ${noDetails} | ${'Canceled deploy to'} | ${noActions}
+ `(
+ '$status + previous: $previous + manual: $deploymentDetails.isManual',
+ ({ status, previous, deploymentDetails, text, actionButtons }) => {
+ beforeEach(() => {
+ const previousOrSuccess = Boolean(previous || status === SUCCESS);
+ const updatedDeploymentData = {
+ status,
+ deployed_at: previous ? deploymentMockData.deployed_at : null,
+ deployed_at_formatted: previous ? deploymentMockData.deployed_at_formatted : null,
+ external_url: previousOrSuccess ? deploymentMockData.external_url : null,
+ external_url_formatted: previousOrSuccess
+ ? deploymentMockData.external_url_formatted
+ : null,
+ stop_url: previousOrSuccess ? deploymentMockData.stop_url : null,
+ details: deploymentDetails,
+ };
+
+ factory({
+ propsData: {
+ showMetrics: false,
+ deployment: {
+ ...deploymentMockData,
+ ...updatedDeploymentData,
+ },
+ },
+ });
+ });
+
+ it(`renders the text: ${text}`, () => {
+ expect(wrapper.find(DeploymentInfo).text()).toContain(text);
+ });
+
+ if (actionButtons.length > 0) {
+ describe('renders the expected button group', () => {
+ actionButtons.forEach(button => {
+ it(`renders ${button.name}`, () => {
+ expect(wrapper.find(button).exists()).toBe(true);
+ });
+ });
+ });
+ }
+
+ if (actionButtons.length === 0) {
+ describe('does not render the button group', () => {
+ [DeploymentViewButton, DeploymentStopButton].forEach(button => {
+ it(`does not render ${button.name}`, () => {
+ expect(wrapper.find(button).exists()).toBe(false);
+ });
+ });
+ });
+ }
+
+ if (actionButtons.includes(DeploymentViewButton)) {
+ it('renders the View button with expected text', () => {
+ if (status === SUCCESS) {
+ expect(wrapper.find(DeploymentViewButton).text()).toContain('View app');
+ } else {
+ expect(wrapper.find(DeploymentViewButton).text()).toContain('View previous app');
+ }
+ });
+ }
+ },
+ );
+ });
+
+ describe('hasExternalUrls', () => {
+ describe('when deployment has both external_url_formatted and external_url', () => {
+ it('should return true', () => {
+ expect(wrapper.vm.hasExternalUrls).toEqual(true);
+ });
+
+ it('should render the View Button', () => {
+ expect(wrapper.find(DeploymentViewButton).exists()).toBe(true);
+ });
+ });
+
+ describe('when deployment has no external_url_formatted', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ deployment: { ...deploymentMockData, external_url_formatted: null },
+ showMetrics: false,
+ },
+ });
+ });
+
+ it('should return false', () => {
+ expect(wrapper.vm.hasExternalUrls).toEqual(false);
+ });
+
+ it('should not render the View Button', () => {
+ expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
+ });
+ });
+
+ describe('when deployment has no external_url', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ deployment: { ...deploymentMockData, external_url: null },
+ showMetrics: false,
+ },
+ });
+ });
+
+ it('should return false', () => {
+ expect(wrapper.vm.hasExternalUrls).toEqual(false);
+ });
+
+ it('should not render the View Button', () => {
+ expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js
new file mode 100644
index 00000000000..6e3c6f64c68
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js
@@ -0,0 +1,118 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
+import ReviewAppLink from '~/vue_merge_request_widget/components/review_app_link.vue';
+import deploymentMockData from './deployment_mock_data';
+
+describe('Deployment View App button', () => {
+ let wrapper;
+
+ const factory = (options = {}) => {
+ const localVue = createLocalVue();
+
+ wrapper = mount(localVue.extend(DeploymentViewButton), {
+ localVue,
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ factory({
+ propsData: {
+ deployment: deploymentMockData,
+ isCurrent: true,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('text', () => {
+ describe('when app is current', () => {
+ it('shows View app', () => {
+ expect(wrapper.find(ReviewAppLink).text()).toContain('View app');
+ });
+ });
+
+ describe('when app is not current', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ deployment: deploymentMockData,
+ isCurrent: false,
+ },
+ });
+ });
+
+ it('shows View Previous app', () => {
+ expect(wrapper.find(ReviewAppLink).text()).toContain('View previous app');
+ });
+ });
+ });
+
+ describe('without changes', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ deployment: { ...deploymentMockData, changes: null },
+ isCurrent: false,
+ },
+ });
+ });
+
+ it('renders the link to the review app without dropdown', () => {
+ expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
+ });
+ });
+
+ describe('with a single change', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ deployment: { ...deploymentMockData, changes: [deploymentMockData.changes[0]] },
+ isCurrent: false,
+ },
+ });
+ });
+
+ it('renders the link to the review app without dropdown', () => {
+ expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
+ });
+
+ it('renders the link to the review app linked to to the first change', () => {
+ const expectedUrl = deploymentMockData.changes[0].external_url;
+ const deployUrl = wrapper.find('.js-deploy-url');
+
+ expect(deployUrl.attributes().href).not.toBeNull();
+ expect(deployUrl.attributes().href).toEqual(expectedUrl);
+ });
+ });
+
+ describe('with multiple changes', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ deployment: deploymentMockData,
+ isCurrent: false,
+ },
+ });
+ });
+
+ it('renders the link to the review app with dropdown', () => {
+ expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(true);
+ });
+
+ it('renders all the links to the review apps', () => {
+ const allUrls = wrapper.findAll('.js-deploy-url-menu-item').wrappers;
+ const expectedUrls = deploymentMockData.changes.map(change => change.external_url);
+
+ expectedUrls.forEach((expectedUrl, idx) => {
+ const deployUrl = allUrls[idx];
+
+ expect(deployUrl.attributes().href).not.toBeNull();
+ expect(deployUrl.attributes().href).toEqual(expectedUrl);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
deleted file mode 100644
index 1949bee1406..00000000000
--- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js
+++ /dev/null
@@ -1,313 +0,0 @@
-import Vue from 'vue';
-import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
-import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-import { getTimeago } from '~/lib/utils/datetime_utility';
-import mountComponent from '../../helpers/vue_mount_component_helper';
-
-describe('Deployment component', () => {
- const Component = Vue.extend(deploymentComponent);
- let deploymentMockData;
-
- beforeEach(() => {
- deploymentMockData = {
- id: 15,
- name: 'review/diplo',
- url: '/root/review-apps/environments/15',
- stop_url: '/root/review-apps/environments/15/stop',
- metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
- metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
- external_url: 'http://gitlab.com.',
- external_url_formatted: 'gitlab',
- deployed_at: '2017-03-22T22:44:42.258Z',
- deployed_at_formatted: 'Mar 22, 2017 10:44pm',
- changes: [
- {
- path: 'index.html',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
- },
- {
- path: 'imgs/gallery.html',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
- },
- {
- path: 'about/',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
- },
- ],
- };
- });
-
- let vm;
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('', () => {
- beforeEach(() => {
- vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: true });
- });
-
- describe('deployTimeago', () => {
- it('return formatted date', () => {
- const readable = getTimeago().format(deploymentMockData.deployed_at);
-
- expect(vm.deployTimeago).toEqual(readable);
- });
- });
-
- describe('hasExternalUrls', () => {
- it('should return true', () => {
- expect(vm.hasExternalUrls).toEqual(true);
- });
-
- it('should return false when deployment has no external_url_formatted', () => {
- vm.deployment.external_url_formatted = null;
-
- expect(vm.hasExternalUrls).toEqual(false);
- });
-
- it('should return false when deployment has no external_url', () => {
- vm.deployment.external_url = null;
-
- expect(vm.hasExternalUrls).toEqual(false);
- });
- });
-
- describe('hasDeploymentTime', () => {
- it('should return true', () => {
- expect(vm.hasDeploymentTime).toEqual(true);
- });
-
- it('should return false when deployment has no deployed_at', () => {
- vm.deployment.deployed_at = null;
-
- expect(vm.hasDeploymentTime).toEqual(false);
- });
-
- it('should return false when deployment has no deployed_at_formatted', () => {
- vm.deployment.deployed_at_formatted = null;
-
- expect(vm.hasDeploymentTime).toEqual(false);
- });
- });
-
- describe('hasDeploymentMeta', () => {
- it('should return true', () => {
- expect(vm.hasDeploymentMeta).toEqual(true);
- });
-
- it('should return false when deployment has no url', () => {
- vm.deployment.url = null;
-
- expect(vm.hasDeploymentMeta).toEqual(false);
- });
-
- it('should return false when deployment has no name', () => {
- vm.deployment.name = null;
-
- expect(vm.hasDeploymentMeta).toEqual(false);
- });
- });
-
- describe('stopEnvironment', () => {
- const url = '/foo/bar';
- const returnPromise = () =>
- new Promise(resolve => {
- resolve({
- data: {
- redirect_url: url,
- },
- });
- });
- const mockStopEnvironment = () => {
- vm.stopEnvironment(deploymentMockData);
- return vm;
- };
-
- it('should show a confirm dialog and call service.stopEnvironment when confirmed', done => {
- spyOn(window, 'confirm').and.returnValue(true);
- spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
- const visitUrl = spyOnDependency(deploymentComponent, 'visitUrl').and.returnValue(true);
- vm = mockStopEnvironment();
-
- expect(window.confirm).toHaveBeenCalled();
- expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
- setTimeout(() => {
- expect(visitUrl).toHaveBeenCalledWith(url);
- done();
- }, 333);
- });
-
- it('should show a confirm dialog but should not work if the dialog is rejected', () => {
- spyOn(window, 'confirm').and.returnValue(false);
- spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
- vm = mockStopEnvironment();
-
- expect(window.confirm).toHaveBeenCalled();
- expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
- });
- });
-
- it('renders deployment name', () => {
- expect(vm.$el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(
- deploymentMockData.url,
- );
-
- expect(vm.$el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
- });
-
- it('renders external URL', () => {
- expect(vm.$el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(
- deploymentMockData.external_url,
- );
-
- expect(vm.$el.querySelector('.js-deploy-url').innerText).toContain('View app');
- });
-
- it('renders stop button', () => {
- expect(vm.$el.querySelector('.btn')).not.toBeNull();
- });
-
- it('renders deployment time', () => {
- expect(vm.$el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
- });
-
- it('renders metrics component', () => {
- expect(vm.$el.querySelector('.js-mr-memory-usage')).not.toBeNull();
- });
- });
-
- describe('with showMetrics enabled', () => {
- beforeEach(() => {
- vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: true });
- });
-
- it('shows metrics', () => {
- expect(vm.$el).toContainElement('.js-mr-memory-usage');
- });
- });
-
- describe('with showMetrics disabled', () => {
- beforeEach(() => {
- vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: false });
- });
-
- it('hides metrics', () => {
- expect(vm.$el).not.toContainElement('.js-mr-memory-usage');
- });
- });
-
- describe('without changes', () => {
- beforeEach(() => {
- delete deploymentMockData.changes;
-
- vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: true });
- });
-
- it('renders the link to the review app without dropdown', () => {
- expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
- expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
- });
- });
-
- describe('with a single change', () => {
- beforeEach(() => {
- deploymentMockData.changes = deploymentMockData.changes.slice(0, 1);
-
- vm = mountComponent(Component, {
- deployment: { ...deploymentMockData },
- showMetrics: true,
- });
- });
-
- it('renders the link to the review app without dropdown', () => {
- expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
- expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
- });
-
- it('renders the link to the review app linked to to the first change', () => {
- const expectedUrl = deploymentMockData.changes[0].external_url;
- const deployUrl = vm.$el.querySelector('.js-deploy-url');
-
- expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
- expect(deployUrl).not.toBeNull();
- expect(deployUrl.href).toEqual(expectedUrl);
- });
- });
-
- describe('deployment status', () => {
- describe('running', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
- deployment: Object.assign({}, deploymentMockData, { status: 'running' }),
- showMetrics: true,
- });
- });
-
- it('renders information about running deployment', () => {
- expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deploying to');
- });
-
- it('renders disabled stop button', () => {
- expect(vm.$el.querySelector('.js-stop-env').getAttribute('disabled')).toBe('disabled');
- });
- });
-
- describe('success', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
- deployment: Object.assign({}, deploymentMockData, { status: 'success' }),
- showMetrics: true,
- });
- });
-
- it('renders information about finished deployment', () => {
- expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deployed to');
- });
- });
-
- describe('failed', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
- deployment: Object.assign({}, deploymentMockData, { status: 'failed' }),
- showMetrics: true,
- });
- });
-
- it('renders information about finished deployment', () => {
- expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain(
- 'Failed to deploy to',
- );
- });
- });
-
- describe('created', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
- deployment: Object.assign({}, deploymentMockData, { status: 'created' }),
- showMetrics: true,
- });
- });
-
- it('renders information about created deployment', () => {
- expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Will deploy to');
- });
- });
-
- describe('canceled', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
- deployment: Object.assign({}, deploymentMockData, { status: 'canceled' }),
- showMetrics: true,
- });
- });
-
- it('renders information about canceled deployment', () => {
- expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain(
- 'Failed to deploy to',
- );
- });
- });
- });
-});
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_stop_button_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_stop_button_spec.js
new file mode 100644
index 00000000000..6a6d8279c2c
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/deployment_stop_button_spec.js
@@ -0,0 +1,95 @@
+import Vue from 'vue';
+import deploymentStopComponent from '~/vue_merge_request_widget/components/deployment/deployment_stop_button.vue';
+import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
+import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Deployment component', () => {
+ const Component = Vue.extend(deploymentStopComponent);
+ let deploymentMockData;
+
+ beforeEach(() => {
+ deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/review-apps/environments/15',
+ stop_url: '/root/review-apps/environments/15/stop',
+ metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
+ external_url: 'http://gitlab.com.',
+ external_url_formatted: 'gitlab',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ deployment_manual_actions: [],
+ status: SUCCESS,
+ changes: [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
+ };
+ });
+
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ stopUrl: deploymentMockData.stop_url,
+ isDeployInProgress: false,
+ });
+ });
+
+ describe('stopEnvironment', () => {
+ const url = '/foo/bar';
+ const returnPromise = () =>
+ new Promise(resolve => {
+ resolve({
+ data: {
+ redirect_url: url,
+ },
+ });
+ });
+ const mockStopEnvironment = () => {
+ vm.stopEnvironment(deploymentMockData);
+ return vm;
+ };
+
+ it('should show a confirm dialog and call service.stopEnvironment when confirmed', done => {
+ spyOn(window, 'confirm').and.returnValue(true);
+ spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
+ const visitUrl = spyOnDependency(deploymentStopComponent, 'visitUrl').and.returnValue(true);
+ vm = mockStopEnvironment();
+
+ expect(window.confirm).toHaveBeenCalled();
+ expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
+ setTimeout(() => {
+ expect(visitUrl).toHaveBeenCalledWith(url);
+ done();
+ }, 333);
+ });
+
+ it('should show a confirm dialog but should not work if the dialog is rejected', () => {
+ spyOn(window, 'confirm').and.returnValue(false);
+ spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
+ vm = mockStopEnvironment();
+
+ expect(window.confirm).toHaveBeenCalled();
+ expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index 99de320ca2b..d15c3552b4a 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import MemoryUsage from '~/vue_merge_request_widget/components/memory_usage.vue';
+import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
diff --git a/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js b/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js
index 069bc14e01e..d8d84d087bc 100644
--- a/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js
@@ -8,6 +8,7 @@ describe('review app link', () => {
const props = {
link: '/review',
cssClass: 'js-link',
+ isCurrent: true,
};
let vm;
let el;
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 40bec001ffe..ff34dafa660 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -1,3 +1,5 @@
+import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
+
export default {
id: 132,
iid: 22,
@@ -290,15 +292,20 @@ export const mockStore = {
name: 'bogus',
external_url: 'https://fake.com',
external_url_formatted: 'https://fake.com',
+ status: SUCCESS,
},
{
id: 1,
name: 'bogus-docs',
external_url: 'https://fake.com',
external_url_formatted: 'https://fake.com',
+ status: SUCCESS,
},
],
- postMergeDeployments: [{ id: 0, name: 'prod' }, { id: 1, name: 'prod-docs' }],
+ postMergeDeployments: [
+ { id: 0, name: 'prod', status: SUCCESS },
+ { id: 1, name: 'prod-docs', status: SUCCESS },
+ ],
troubleshootingDocsPath: 'troubleshooting-docs-path',
ciStatus: 'ci-status',
hasCI: true,
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index 30e0504e4e1..604b21e77fe 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -6,6 +6,7 @@ import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './mock_data';
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
+import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
const returnPromise = data =>
new Promise(resolve => {
@@ -277,7 +278,9 @@ describe('mrWidgetOptions', () => {
describe('fetchDeployments', () => {
it('should fetch deployments', done => {
- spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
+ spyOn(vm.service, 'fetchDeployments').and.returnValue(
+ returnPromise([{ id: 1, status: SUCCESS }]),
+ );
vm.fetchPreMergeDeployments();
@@ -554,7 +557,7 @@ describe('mrWidgetOptions', () => {
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
changes,
- status: 'success',
+ status: SUCCESS,
};
beforeEach(done => {
diff --git a/spec/lib/api/entities/release_spec.rb b/spec/lib/api/entities/release_spec.rb
new file mode 100644
index 00000000000..729a69347cb
--- /dev/null
+++ b/spec/lib/api/entities/release_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Entities::Release do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project) }
+ let(:user) { create(:user) }
+ let(:entity) { described_class.new(release, current_user: user) }
+
+ subject { entity.as_json }
+
+ describe 'evidence' do
+ context 'when the current user can download code' do
+ it 'exposes the evidence sha and the json path' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(user, :download_code, project).and_return(true)
+
+ expect(subject[:evidence_sha]).to eq(release.evidence_sha)
+ expect(subject[:assets][:evidence_file_path]).to eq(
+ Gitlab::Routing.url_helpers.evidence_project_release_url(project,
+ release.tag,
+ format: :json)
+ )
+ end
+ end
+
+ context 'when the current user cannot download code' do
+ it 'does not expose any evidence data' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(user, :download_code, project).and_return(false)
+
+ expect(subject.keys).not_to include(:evidence_sha)
+ expect(subject[:assets].keys).not_to include(:evidence_file_path)
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 95435c6dabd..03340f34305 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -4063,4 +4063,54 @@ describe Ci::Build do
expect(job.invalid_dependencies).to eq([pre_stage_job_invalid])
end
end
+
+ describe '#execute_hooks' do
+ context 'with project hooks' do
+ before do
+ create(:project_hook, project: project, job_events: true)
+ end
+
+ it 'execute hooks' do
+ expect_any_instance_of(ProjectHook).to receive(:async_execute)
+
+ build.execute_hooks
+ end
+ end
+
+ context 'without relevant project hooks' do
+ before do
+ create(:project_hook, project: project, job_events: false)
+ end
+
+ it 'does not execute a hook' do
+ expect_any_instance_of(ProjectHook).not_to receive(:async_execute)
+
+ build.execute_hooks
+ end
+ end
+
+ context 'with project services' do
+ before do
+ create(:service, active: true, job_events: true, project: project)
+ end
+
+ it 'execute services' do
+ expect_any_instance_of(Service).to receive(:async_execute)
+
+ build.execute_hooks
+ end
+ end
+
+ context 'without relevant project services' do
+ before do
+ create(:service, active: true, job_events: false, project: project)
+ end
+
+ it 'execute services' do
+ expect_any_instance_of(Service).not_to receive(:async_execute)
+
+ build.execute_hooks
+ end
+ end
+ end
end
diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb
index 2c012f080dd..38f441fbc4d 100644
--- a/spec/services/cohorts_service_spec.rb
+++ b/spec/services/cohorts_service_spec.rb
@@ -22,73 +22,73 @@ describe CohortsService do
expected_cohorts = [
{
registration_month: month_start(11),
- activity_months: Array.new(12) { { total: 0, percentage: 0 } },
+ activity_months: Array.new(11) { { total: 0, percentage: 0 } },
total: 0,
inactive: 0
},
{
registration_month: month_start(10),
- activity_months: [{ total: 2, percentage: 100 }] + Array.new(10) { { total: 1, percentage: 50 } },
+ activity_months: Array.new(10) { { total: 1, percentage: 50 } },
total: 2,
inactive: 0
},
{
registration_month: month_start(9),
- activity_months: Array.new(10) { { total: 0, percentage: 0 } },
+ activity_months: Array.new(9) { { total: 0, percentage: 0 } },
total: 0,
inactive: 0
},
{
registration_month: month_start(8),
- activity_months: [{ total: 2, percentage: 100 }] + Array.new(8) { { total: 1, percentage: 50 } },
+ activity_months: Array.new(8) { { total: 1, percentage: 50 } },
total: 2,
inactive: 0
},
{
registration_month: month_start(7),
- activity_months: Array.new(8) { { total: 0, percentage: 0 } },
+ activity_months: Array.new(7) { { total: 0, percentage: 0 } },
total: 0,
inactive: 0
},
{
registration_month: month_start(6),
- activity_months: [{ total: 2, percentage: 100 }] + Array.new(6) { { total: 1, percentage: 50 } },
+ activity_months: Array.new(6) { { total: 1, percentage: 50 } },
total: 2,
inactive: 0
},
{
registration_month: month_start(5),
- activity_months: Array.new(6) { { total: 0, percentage: 0 } },
+ activity_months: Array.new(5) { { total: 0, percentage: 0 } },
total: 0,
inactive: 0
},
{
registration_month: month_start(4),
- activity_months: [{ total: 2, percentage: 100 }] + Array.new(4) { { total: 1, percentage: 50 } },
+ activity_months: Array.new(4) { { total: 1, percentage: 50 } },
total: 2,
inactive: 0
},
{
registration_month: month_start(3),
- activity_months: Array.new(4) { { total: 0, percentage: 0 } },
+ activity_months: Array.new(3) { { total: 0, percentage: 0 } },
total: 0,
inactive: 0
},
{
registration_month: month_start(2),
- activity_months: [{ total: 2, percentage: 100 }] + Array.new(2) { { total: 1, percentage: 50 } },
+ activity_months: Array.new(2) { { total: 1, percentage: 50 } },
total: 2,
inactive: 0
},
{
registration_month: month_start(1),
- activity_months: Array.new(2) { { total: 0, percentage: 0 } },
+ activity_months: Array.new(1) { { total: 0, percentage: 0 } },
total: 0,
inactive: 0
},
{
registration_month: month_start(0),
- activity_months: [{ total: 2, percentage: 100 }],
+ activity_months: [],
total: 2,
inactive: 1
}