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>2021-06-02 18:09:59 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-06-02 18:09:59 +0300
commit1c2ff01b694fd06be15bc20279eef71ee5adf402 (patch)
tree98a588172ab8021790538a515933cf83552c5086 /spec
parent2e4e6e9bb63212c628e67c6865fa39f62217a83d (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb4
-rw-r--r--spec/frontend/api_spec.js18
-rw-r--r--spec/frontend/fixtures/startup_css.rb14
-rw-r--r--spec/frontend/packages/details/components/app_spec.js104
-rw-r--r--spec/frontend/packages/details/components/package_files_spec.js57
-rw-r--r--spec/frontend/packages/details/store/actions_spec.js62
-rw-r--r--spec/frontend/packages/details/store/mutations_spec.js9
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_form_spec.js35
-rw-r--r--spec/lib/gitlab/ci/yaml_processor/result_spec.rb39
-rw-r--r--spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb31
-rw-r--r--spec/models/ci/build_spec.rb14
-rw-r--r--spec/models/ci/runner_spec.rb30
-rw-r--r--spec/models/concerns/limitable_spec.rb25
-rw-r--r--spec/presenters/packages/detail/package_presenter_spec.rb3
-rw-r--r--spec/requests/api/ci/runner/jobs_put_spec.rb70
-rw-r--r--spec/requests/api/ci/runner/runners_post_spec.rb37
-rw-r--r--spec/services/ci/update_build_state_service_spec.rb24
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb22
18 files changed, 445 insertions, 153 deletions
diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
index 4c42800cf05..c002d199b01 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe 'User uses header search field', :js do
expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
- context 'when clicking issues' do
+ context 'when clicking issues', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332317' do
let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
it 'shows assigned issues' do
@@ -75,7 +75,7 @@ RSpec.describe 'User uses header search field', :js do
end
end
- context 'when clicking merge requests' do
+ context 'when clicking merge requests', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332317' do
let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
it 'shows assigned merge requests' do
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 0722dcde0a3..f708d8c7728 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -116,6 +116,24 @@ describe('Api', () => {
});
});
});
+
+ describe('deleteProjectPackageFile', () => {
+ const packageFileId = 'package_file_id';
+
+ it('delete a package', () => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/packages/${packageId}/package_files/${packageFileId}`;
+
+ jest.spyOn(axios, 'delete');
+ mock.onDelete(expectedUrl).replyOnce(httpStatus.OK, true);
+
+ return Api.deleteProjectPackageFile(projectId, packageId, packageFileId).then(
+ ({ data }) => {
+ expect(data).toEqual(true);
+ expect(axios.delete).toHaveBeenCalledWith(expectedUrl);
+ },
+ );
+ });
+ });
});
describe('container registry', () => {
diff --git a/spec/frontend/fixtures/startup_css.rb b/spec/frontend/fixtures/startup_css.rb
index 134d29d3106..003f7b768dd 100644
--- a/spec/frontend/fixtures/startup_css.rb
+++ b/spec/frontend/fixtures/startup_css.rb
@@ -11,12 +11,13 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
before(:all) do
stub_feature_flags(combined_menu: true)
+ stub_feature_flags(sidebar_refactor: true)
clean_frontend_fixtures('startup_css/')
end
shared_examples 'startup css project fixtures' do |type|
let(:user) { create(:user, :admin) }
- let(:project) { create(:project, :public, :repository, description: 'Code and stuff', avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png'), creator: user) }
+ let(:project) { create(:project, :public, :repository, description: 'Code and stuff', creator: user) }
before do
sign_in(user)
@@ -42,6 +43,17 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
expect(response).to be_successful
end
+ it "startup_css/project-#{type}-legacy-sidebar.html" do
+ stub_feature_flags(sidebar_refactor: false)
+
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ id: project
+ }
+
+ expect(response).to be_successful
+ end
+
it "startup_css/project-#{type}-signed-out.html" do
sign_out(user)
diff --git a/spec/frontend/packages/details/components/app_spec.js b/spec/frontend/packages/details/components/app_spec.js
index 11dad7ba34d..4b890f868f4 100644
--- a/spec/frontend/packages/details/components/app_spec.js
+++ b/spec/frontend/packages/details/components/app_spec.js
@@ -1,4 +1,4 @@
-import { GlEmptyState, GlModal } from '@gitlab/ui';
+import { GlEmptyState } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import stubChildren from 'helpers/stub_children';
@@ -34,6 +34,7 @@ describe('PackagesApp', () => {
let store;
const fetchPackageVersions = jest.fn();
const deletePackage = jest.fn();
+ const deletePackageFile = jest.fn();
const defaultProjectName = 'bar';
const { location } = window;
@@ -59,6 +60,7 @@ describe('PackagesApp', () => {
actions: {
deletePackage,
fetchPackageVersions,
+ deletePackageFile,
},
getters,
});
@@ -82,8 +84,8 @@ describe('PackagesApp', () => {
const packageTitle = () => wrapper.find(PackageTitle);
const emptyState = () => wrapper.find(GlEmptyState);
const deleteButton = () => wrapper.find('.js-delete-button');
- const deleteModal = () => wrapper.find(GlModal);
- const modalDeleteButton = () => wrapper.find({ ref: 'modal-delete-button' });
+ const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' });
+ const findDeleteFileModal = () => wrapper.find({ ref: 'deleteFileModal' });
const versionsTab = () => wrapper.find('.js-versions-tab > a');
const packagesLoader = () => wrapper.find(PackagesListLoader);
const packagesVersionRows = () => wrapper.findAll(PackageListRow);
@@ -110,7 +112,7 @@ describe('PackagesApp', () => {
it('renders the app and displays the package title', () => {
createComponent();
- expect(packageTitle()).toExist();
+ expect(packageTitle().exists()).toBe(true);
});
it('renders an empty state component when no an invalid package is passed as a prop', () => {
@@ -118,7 +120,7 @@ describe('PackagesApp', () => {
packageEntity: {},
});
- expect(emptyState()).toExist();
+ expect(emptyState().exists()).toBe(true);
});
it('package history has the right props', () => {
@@ -152,7 +154,16 @@ describe('PackagesApp', () => {
});
it('shows the delete confirmation modal when delete is clicked', () => {
- expect(deleteModal()).toExist();
+ expect(findDeleteModal().exists()).toBe(true);
+ });
+ });
+
+ describe('deleting package files', () => {
+ it('shows the delete confirmation modal when delete is clicked', () => {
+ createComponent();
+ findPackageFiles().vm.$emit('delete-file', mavenFiles[0]);
+
+ expect(findDeleteFileModal().exists()).toBe(true);
});
});
@@ -228,13 +239,7 @@ describe('PackagesApp', () => {
});
describe('tracking and delete', () => {
- const doDelete = async () => {
- deleteButton().trigger('click');
- await wrapper.vm.$nextTick();
- modalDeleteButton().trigger('click');
- };
-
- describe('delete', () => {
+ describe('delete package', () => {
const originalReferrer = document.referrer;
const setReferrer = (value = defaultProjectName) => {
Object.defineProperty(document, 'referrer', {
@@ -250,9 +255,9 @@ describe('PackagesApp', () => {
});
});
- it('calls the proper vuex action', async () => {
+ it('calls the proper vuex action', () => {
createComponent({ packageEntity: npmPackage });
- await doDelete();
+ findDeleteModal().vm.$emit('primary');
expect(deletePackage).toHaveBeenCalled();
});
@@ -260,7 +265,7 @@ describe('PackagesApp', () => {
setReferrer();
deletePackage.mockResolvedValue();
createComponent({ packageEntity: npmPackage });
- await doDelete();
+ findDeleteModal().vm.$emit('primary');
await deletePackage();
expect(window.location.replace).toHaveBeenCalledWith(
'project_url?showSuccessDeleteAlert=true',
@@ -271,7 +276,7 @@ describe('PackagesApp', () => {
setReferrer('baz');
deletePackage.mockResolvedValue();
createComponent({ packageEntity: npmPackage });
- await doDelete();
+ findDeleteModal().vm.$emit('primary');
await deletePackage();
expect(window.location.replace).toHaveBeenCalledWith(
'group_url?showSuccessDeleteAlert=true',
@@ -279,6 +284,17 @@ describe('PackagesApp', () => {
});
});
+ describe('delete file', () => {
+ it('calls the proper vuex action', () => {
+ createComponent({ packageEntity: npmPackage });
+
+ findPackageFiles().vm.$emit('delete-file', mavenFiles[0]);
+ findDeleteFileModal().vm.$emit('primary');
+
+ expect(deletePackageFile).toHaveBeenCalled();
+ });
+ });
+
describe('tracking', () => {
let eventSpy;
let utilSpy;
@@ -295,9 +311,9 @@ describe('PackagesApp', () => {
expect(utilSpy).toHaveBeenCalledWith('conan');
});
- it(`delete button on delete modal call event with ${TrackingActions.DELETE_PACKAGE}`, async () => {
+ it(`delete button on delete modal call event with ${TrackingActions.DELETE_PACKAGE}`, () => {
createComponent({ packageEntity: npmPackage });
- await doDelete();
+ findDeleteModal().vm.$emit('primary');
expect(eventSpy).toHaveBeenCalledWith(
category,
TrackingActions.DELETE_PACKAGE,
@@ -305,6 +321,56 @@ describe('PackagesApp', () => {
);
});
+ it(`canceling a package deletion tracks ${TrackingActions.CANCEL_DELETE_PACKAGE}`, () => {
+ createComponent({ packageEntity: npmPackage });
+
+ findDeleteModal().vm.$emit('canceled');
+
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ TrackingActions.CANCEL_DELETE_PACKAGE,
+ expect.any(Object),
+ );
+ });
+
+ it(`request a file deletion tracks ${TrackingActions.REQUEST_DELETE_PACKAGE_FILE}`, () => {
+ createComponent({ packageEntity: npmPackage });
+
+ findPackageFiles().vm.$emit('delete-file', mavenFiles[0]);
+
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ TrackingActions.REQUEST_DELETE_PACKAGE_FILE,
+ expect.any(Object),
+ );
+ });
+
+ it(`confirming a file deletion tracks ${TrackingActions.DELETE_PACKAGE_FILE}`, () => {
+ createComponent({ packageEntity: npmPackage });
+
+ findPackageFiles().vm.$emit('delete-file', npmPackage);
+ findDeleteFileModal().vm.$emit('primary');
+
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ TrackingActions.REQUEST_DELETE_PACKAGE_FILE,
+ expect.any(Object),
+ );
+ });
+
+ it(`canceling a file deletion tracks ${TrackingActions.CANCEL_DELETE_PACKAGE_FILE}`, () => {
+ createComponent({ packageEntity: npmPackage });
+
+ findPackageFiles().vm.$emit('delete-file', npmPackage);
+ findDeleteFileModal().vm.$emit('canceled');
+
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ TrackingActions.CANCEL_DELETE_PACKAGE_FILE,
+ expect.any(Object),
+ );
+ });
+
it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => {
createComponent({ packageEntity: conanPackage });
diff --git a/spec/frontend/packages/details/components/package_files_spec.js b/spec/frontend/packages/details/components/package_files_spec.js
index bcf1b6d56f0..494aa631d9d 100644
--- a/spec/frontend/packages/details/components/package_files_spec.js
+++ b/spec/frontend/packages/details/components/package_files_spec.js
@@ -1,3 +1,4 @@
+import { GlDropdown } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import stubChildren from 'helpers/stub_children';
import component from '~/packages/details/components/package_files.vue';
@@ -12,16 +13,19 @@ describe('Package Files', () => {
const findAllRows = () => wrapper.findAll('[data-testid="file-row"');
const findFirstRow = () => findAllRows().at(0);
const findSecondRow = () => findAllRows().at(1);
- const findFirstRowDownloadLink = () => findFirstRow().find('[data-testid="download-link"');
- const findFirstRowCommitLink = () => findFirstRow().find('[data-testid="commit-link"');
- const findSecondRowCommitLink = () => findSecondRow().find('[data-testid="commit-link"');
+ const findFirstRowDownloadLink = () => findFirstRow().find('[data-testid="download-link"]');
+ const findFirstRowCommitLink = () => findFirstRow().find('[data-testid="commit-link"]');
+ const findSecondRowCommitLink = () => findSecondRow().find('[data-testid="commit-link"]');
const findFirstRowFileIcon = () => findFirstRow().find(FileIcon);
const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip);
+ const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown);
+ const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]');
- const createComponent = (packageFiles = npmFiles) => {
+ const createComponent = ({ packageFiles = npmFiles, canDelete = true } = {}) => {
wrapper = mount(component, {
propsData: {
packageFiles,
+ canDelete,
},
stubs: {
...stubChildren(component),
@@ -43,7 +47,7 @@ describe('Package Files', () => {
});
it('renders multiple files for a package that contains more than one file', () => {
- createComponent(mavenFiles);
+ createComponent({ packageFiles: mavenFiles });
expect(findAllRows()).toHaveLength(2);
});
@@ -123,7 +127,7 @@ describe('Package Files', () => {
});
describe('when package file has no pipeline associated', () => {
it('does not exist', () => {
- createComponent(mavenFiles);
+ createComponent({ packageFiles: mavenFiles });
expect(findFirstRowCommitLink().exists()).toBe(false);
});
@@ -131,11 +135,50 @@ describe('Package Files', () => {
describe('when only one file lacks an associated pipeline', () => {
it('renders the commit when it exists and not otherwise', () => {
- createComponent([npmFiles[0], mavenFiles[0]]);
+ createComponent({ packageFiles: [npmFiles[0], mavenFiles[0]] });
expect(findFirstRowCommitLink().exists()).toBe(true);
expect(findSecondRowCommitLink().exists()).toBe(false);
});
});
+
+ describe('action menu', () => {
+ describe('when the user can delete', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstActionMenu().exists()).toBe(true);
+ });
+
+ describe('menu items', () => {
+ describe('delete file', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findActionMenuDelete().exists()).toBe(true);
+ });
+
+ it('emits a delete event when clicked', () => {
+ createComponent();
+
+ findActionMenuDelete().vm.$emit('click');
+
+ const [[{ id }]] = wrapper.emitted('delete-file');
+ expect(id).toBe(npmFiles[0].id);
+ });
+ });
+ });
+ });
+
+ describe('when the user can not delete', () => {
+ const canDelete = false;
+
+ it('does not exist', () => {
+ createComponent({ canDelete });
+
+ expect(findFirstActionMenu().exists()).toBe(false);
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/packages/details/store/actions_spec.js b/spec/frontend/packages/details/store/actions_spec.js
index d11ee548b72..b16e50debc4 100644
--- a/spec/frontend/packages/details/store/actions_spec.js
+++ b/spec/frontend/packages/details/store/actions_spec.js
@@ -1,10 +1,18 @@
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
+import createFlash from '~/flash';
import { FETCH_PACKAGE_VERSIONS_ERROR } from '~/packages/details/constants';
-import { fetchPackageVersions, deletePackage } from '~/packages/details/store/actions';
+import {
+ fetchPackageVersions,
+ deletePackage,
+ deletePackageFile,
+} from '~/packages/details/store/actions';
import * as types from '~/packages/details/store/mutation_types';
-import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages/shared/constants';
+import {
+ DELETE_PACKAGE_ERROR_MESSAGE,
+ DELETE_PACKAGE_FILE_ERROR_MESSAGE,
+ DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
+} from '~/packages/shared/constants';
import { npmPackage as packageEntity } from '../../mock_data';
jest.mock('~/flash.js');
@@ -74,7 +82,10 @@ describe('Actions Package details store', () => {
packageEntity.project_id,
packageEntity.id,
);
- expect(createFlash).toHaveBeenCalledWith(FETCH_PACKAGE_VERSIONS_ERROR);
+ expect(createFlash).toHaveBeenCalledWith({
+ message: FETCH_PACKAGE_VERSIONS_ERROR,
+ type: 'warning',
+ });
done();
},
);
@@ -96,7 +107,48 @@ describe('Actions Package details store', () => {
Api.deleteProjectPackage = jest.fn().mockRejectedValue();
testAction(deletePackage, undefined, { packageEntity }, [], [], () => {
- expect(createFlash).toHaveBeenCalledWith(DELETE_PACKAGE_ERROR_MESSAGE);
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DELETE_PACKAGE_ERROR_MESSAGE,
+ type: 'warning',
+ });
+ done();
+ });
+ });
+ });
+
+ describe('deletePackageFile', () => {
+ const fileId = 'a_file_id';
+
+ it('should call Api.deleteProjectPackageFile and commit the right data', (done) => {
+ const packageFiles = [{ id: 'foo' }, { id: fileId }];
+ Api.deleteProjectPackageFile = jest.fn().mockResolvedValue();
+ testAction(
+ deletePackageFile,
+ fileId,
+ { packageEntity, packageFiles },
+ [{ type: types.UPDATE_PACKAGE_FILES, payload: [{ id: 'foo' }] }],
+ [],
+ () => {
+ expect(Api.deleteProjectPackageFile).toHaveBeenCalledWith(
+ packageEntity.project_id,
+ packageEntity.id,
+ fileId,
+ );
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
+ type: 'success',
+ });
+ done();
+ },
+ );
+ });
+ it('should create flash on API error', (done) => {
+ Api.deleteProjectPackageFile = jest.fn().mockRejectedValue();
+ testAction(deletePackageFile, fileId, { packageEntity }, [], [], () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DELETE_PACKAGE_FILE_ERROR_MESSAGE,
+ type: 'warning',
+ });
done();
});
});
diff --git a/spec/frontend/packages/details/store/mutations_spec.js b/spec/frontend/packages/details/store/mutations_spec.js
index 6bc5fb7241f..296ed02d786 100644
--- a/spec/frontend/packages/details/store/mutations_spec.js
+++ b/spec/frontend/packages/details/store/mutations_spec.js
@@ -28,4 +28,13 @@ describe('Mutations package details Store', () => {
expect(mockState.packageEntity.versions).toEqual(fakeVersions);
});
});
+ describe('UPDATE_PACKAGE_FILES', () => {
+ it('should update the packageFiles', () => {
+ const files = [1, 2, 3];
+
+ mutations[types.UPDATE_PACKAGE_FILES](mockState, files);
+
+ expect(mockState.packageFiles).toEqual(files);
+ });
+ });
});
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
index 79a0ab006da..03338b1930c 100644
--- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
+++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
@@ -1,4 +1,4 @@
-import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadio } from '@gitlab/ui';
+import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadio, GlFormSelect } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
@@ -89,6 +89,7 @@ describe('ForkForm component', () => {
axiosMock.restore();
});
+ const findFormSelect = () => wrapper.find(GlFormSelect);
const findPrivateRadio = () => wrapper.find('[data-testid="radio-private"]');
const findInternalRadio = () => wrapper.find('[data-testid="radio-internal"]');
const findPublicRadio = () => wrapper.find('[data-testid="radio-public"]');
@@ -229,6 +230,37 @@ describe('ForkForm component', () => {
expect(wrapper.findAll(GlFormRadio)).toHaveLength(3);
});
+ it('resets the visibility to default "private" when the namespace is changed', async () => {
+ const namespaces = [
+ {
+ visibility: 'private',
+ },
+ {
+ visibility: 'internal',
+ },
+ {
+ visibility: 'public',
+ },
+ ];
+
+ mockGetRequest();
+ createComponent(
+ {
+ projectVisibility: 'public',
+ },
+ {
+ namespaces,
+ },
+ );
+
+ expect(wrapper.vm.form.fields.visibility.value).toBe('public');
+ findFormSelect().vm.$emit('input', namespaces[1]);
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.form.fields.visibility.value).toBe('private');
+ });
+
it.each`
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}
@@ -324,7 +356,6 @@ describe('ForkForm component', () => {
await submitForm();
- expect(wrapper.find('[name="visibility"]:checked').exists()).toBe(false);
expect(axios.post).not.toHaveBeenCalled();
});
});
diff --git a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
index e345cd4de9b..a0a79dcad36 100644
--- a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
@@ -39,6 +39,45 @@ module Gitlab
expect(expanded_config).to include(*included_config.keys)
end
end
+
+ describe '#yaml_variables_for' do
+ let(:config_content) do
+ <<~YAML
+ variables:
+ VAR1: value 1
+ VAR2: value 2
+
+ job:
+ script: echo 'hello'
+ variables:
+ VAR1: value 11
+ YAML
+ end
+
+ subject(:yaml_variables_for) { result.yaml_variables_for(:job) }
+
+ it do
+ is_expected.to match_array([
+ { key: 'VAR1', value: 'value 11', public: true },
+ { key: 'VAR2', value: 'value 2', public: true }
+ ])
+ end
+ end
+
+ describe '#stage_for' do
+ let(:config_content) do
+ <<~YAML
+ job:
+ script: echo 'hello'
+ YAML
+ end
+
+ subject(:stage_for) { result.stage_for(:job) }
+
+ it do
+ is_expected.to eq('test')
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb
index 0fb3a69df05..8e02f4f562c 100644
--- a/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb
@@ -25,34 +25,6 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Aggregate, :clean_gitlab_redi
end
context 'aggregated_metrics_data' do
- shared_examples 'db sourced aggregated metrics without database_sourced_aggregated_metrics feature' do
- before do
- allow_next_instance_of(described_class) do |instance|
- allow(instance).to receive(:aggregated_metrics).and_return(aggregated_metrics)
- end
- end
-
- context 'with disabled database_sourced_aggregated_metrics feature flag' do
- before do
- stub_feature_flags(database_sourced_aggregated_metrics: false)
- end
-
- let(:aggregated_metrics) do
- [
- aggregated_metric(name: "gmau_2", source: "database", time_frame: time_frame)
- ]
- end
-
- it 'skips database sourced metrics', :aggregate_failures do
- results = {}
- params = { start_date: start_date, end_date: end_date, recorded_at: recorded_at }
-
- expect(sources::PostgresHll).not_to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event1 event2 event3]))
- expect(aggregated_metrics_data).to eq(results)
- end
- end
- end
-
shared_examples 'aggregated_metrics_data' do
context 'no aggregated metric is defined' do
it 'returns empty hash' do
@@ -237,7 +209,6 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Aggregate, :clean_gitlab_redi
let(:time_frame) { ['all'] }
it_behaves_like 'database_sourced_aggregated_metrics'
- it_behaves_like 'db sourced aggregated metrics without database_sourced_aggregated_metrics feature'
context 'redis sourced aggregated metrics' do
let(:aggregated_metrics) { [aggregated_metric(name: 'gmau_1', time_frame: time_frame)] }
@@ -278,7 +249,6 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Aggregate, :clean_gitlab_redi
it_behaves_like 'database_sourced_aggregated_metrics'
it_behaves_like 'redis_sourced_aggregated_metrics'
- it_behaves_like 'db sourced aggregated metrics without database_sourced_aggregated_metrics feature'
end
describe '.aggregated_metrics_monthly_data' do
@@ -289,7 +259,6 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Aggregate, :clean_gitlab_redi
it_behaves_like 'database_sourced_aggregated_metrics'
it_behaves_like 'redis_sourced_aggregated_metrics'
- it_behaves_like 'db sourced aggregated metrics without database_sourced_aggregated_metrics feature'
end
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index c2f49b10639..d5694020b5f 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -320,6 +320,20 @@ RSpec.describe Ci::Build do
end
end
+ describe '#stick_build_if_status_changed' do
+ it 'sticks the build if the status changed' do
+ job = create(:ci_build, :pending)
+
+ allow(Gitlab::Database::LoadBalancing).to receive(:enable?)
+ .and_return(true)
+
+ expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:stick)
+ .with(:build, job.id)
+
+ job.update!(status: :running)
+ end
+ end
+
describe '#enqueue' do
let(:build) { create(:ci_build, :created) }
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 491088a44a1..ab7076e037e 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -273,6 +273,20 @@ RSpec.describe Ci::Runner do
end
end
+ describe '.recent' do
+ subject { described_class.recent }
+
+ before do
+ @runner1 = create(:ci_runner, :instance, contacted_at: nil, created_at: 2.months.ago)
+ @runner2 = create(:ci_runner, :instance, contacted_at: nil, created_at: 3.months.ago)
+ @runner3 = create(:ci_runner, :instance, contacted_at: 1.month.ago, created_at: 2.months.ago)
+ @runner4 = create(:ci_runner, :instance, contacted_at: 1.month.ago, created_at: 3.months.ago)
+ @runner5 = create(:ci_runner, :instance, contacted_at: 3.months.ago, created_at: 5.months.ago)
+ end
+
+ it { is_expected.to eq([@runner1, @runner3, @runner4])}
+ end
+
describe '.online' do
subject { described_class.online }
@@ -365,6 +379,22 @@ RSpec.describe Ci::Runner do
it { is_expected.to eq([@runner1])}
end
+ describe '#tick_runner_queue' do
+ it 'sticks the runner to the primary and calls the original method' do
+ runner = create(:ci_runner)
+
+ allow(Gitlab::Database::LoadBalancing).to receive(:enable?)
+ .and_return(true)
+
+ expect(Gitlab::Database::LoadBalancing::Sticking).to receive(:stick)
+ .with(:runner, runner.id)
+
+ expect(Gitlab::Workhorse).to receive(:set_key_and_notify)
+
+ runner.tick_runner_queue
+ end
+ end
+
describe '#can_pick?' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/models/concerns/limitable_spec.rb b/spec/models/concerns/limitable_spec.rb
index 753e2a8ee5e..6b25ed39efb 100644
--- a/spec/models/concerns/limitable_spec.rb
+++ b/spec/models/concerns/limitable_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
+require 'active_model'
RSpec.describe Limitable do
let(:minimal_test_class) do
@@ -35,6 +36,28 @@ RSpec.describe Limitable do
instance.valid?(:create)
end
+
+ context 'with custom relation' do
+ before do
+ MinimalTestClass.limit_relation = :custom_relation
+ end
+
+ it 'triggers custom limit_relation' do
+ instance = MinimalTestClass.new
+
+ def instance.project
+ @project ||= Object.new
+ end
+
+ limits = Object.new
+ custom_relation = Object.new
+ expect(instance).to receive(:custom_relation).and_return(custom_relation)
+ expect(instance.project).to receive(:actual_limits).and_return(limits)
+ expect(limits).to receive(:exceeded?).with(instance.class.name.demodulize.tableize, custom_relation).and_return(false)
+
+ instance.valid?(:create)
+ end
+ end
end
context 'with global limit' do
diff --git a/spec/presenters/packages/detail/package_presenter_spec.rb b/spec/presenters/packages/detail/package_presenter_spec.rb
index d8f1c98e762..3009f2bd56d 100644
--- a/spec/presenters/packages/detail/package_presenter_spec.rb
+++ b/spec/presenters/packages/detail/package_presenter_spec.rb
@@ -19,7 +19,8 @@ RSpec.describe ::Packages::Detail::PackagePresenter do
size: file.size,
file_md5: file.file_md5,
file_sha1: file.file_sha1,
- file_sha256: file.file_sha256
+ file_sha256: file.file_sha256,
+ id: file.id
}
end
end
diff --git a/spec/requests/api/ci/runner/jobs_put_spec.rb b/spec/requests/api/ci/runner/jobs_put_spec.rb
index 3d5021fba08..8c95748aa5f 100644
--- a/spec/requests/api/ci/runner/jobs_put_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_put_spec.rb
@@ -17,18 +17,14 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
describe '/api/v4/jobs' do
- let(:group) { create(:group, :nested) }
- let(:project) { create(:project, namespace: group, shared_runners_enabled: false) }
- let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
- let(:runner) { create(:ci_runner, :project, projects: [project]) }
- let(:user) { create(:user) }
- let(:job) do
- create(:ci_build, :artifacts, :extended_options,
- pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
- end
+ let_it_be(:group) { create(:group, :nested) }
+ let_it_be(:project) { create(:project, namespace: group, shared_runners_enabled: false) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
+ let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let_it_be(:user) { create(:user) }
describe 'PUT /api/v4/jobs/:id' do
- let(:job) do
+ let_it_be_with_reload(:job) do
create(:ci_build, :pending, :trace_live, pipeline: pipeline, project: project, user: user, runner_id: runner.id)
end
@@ -204,53 +200,6 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when trace is given' do
- it 'creates a trace artifact' do
- allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do
- ArchiveTraceWorker.new.perform(job.id)
- end
-
- update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
-
- job.reload
- expect(response).to have_gitlab_http_status(:ok)
- expect(job.trace.raw).to eq 'BUILD TRACE UPDATED'
- expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED'
- end
-
- context 'when concurrent update of trace is happening' do
- before do
- job.trace.write('wb') do
- update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
- end
- end
-
- it 'returns that operation conflicts' do
- expect(response).to have_gitlab_http_status(:conflict)
- end
- end
- end
-
- context 'when no trace is given' do
- it 'does not override trace information' do
- update_job
-
- expect(job.reload.trace.raw).to eq 'BUILD TRACE'
- end
-
- context 'when running state is sent' do
- it 'updates update_at value' do
- expect { update_job_after_time }.to change { job.reload.updated_at }
- end
- end
-
- context 'when other state is sent' do
- it "doesn't update update_at value" do
- expect { update_job_after_time(20.minutes, state: 'success') }.not_to change { job.reload.updated_at }
- end
- end
- end
-
context 'when job has been erased' do
let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
@@ -267,20 +216,19 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
job.drop!(:script_failure)
end
- it 'does not update job status and job trace' do
- update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
+ it 'does not update job status' do
+ update_job(state: 'success')
job.reload
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.header['Job-Status']).to eq 'failed'
- expect(job.trace.raw).to eq 'Job failed'
expect(job).to be_failed
end
end
context 'when job does not exist anymore' do
it 'returns 403 Forbidden' do
- update_job(non_existing_record_id, state: 'success', trace: 'BUILD TRACE UPDATED')
+ update_job(non_existing_record_id, state: 'success')
expect(response).to have_gitlab_http_status(:forbidden)
end
diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb
index b38630183f4..1696fe63d5d 100644
--- a/spec/requests/api/ci/runner/runners_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_post_spec.rb
@@ -94,7 +94,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
context 'when it exceeds the application limits' do
before do
- create(:ci_runner, runner_type: :project_type, projects: [project])
+ create(:ci_runner, runner_type: :project_type, projects: [project], contacted_at: 1.second.ago)
create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
end
@@ -106,6 +106,22 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(project.runners.reload.size).to eq(1)
end
end
+
+ context 'when abandoned runners cause application limits to not be exceeded' do
+ before do
+ create(:ci_runner, runner_type: :project_type, projects: [project], created_at: 14.months.ago, contacted_at: 13.months.ago)
+ create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
+ end
+
+ it 'creates runner' do
+ request
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['message']).to be_nil
+ expect(project.runners.reload.size).to eq(2)
+ expect(project.runners.recent.size).to eq(1)
+ end
+ end
end
context 'when group token is used' do
@@ -135,7 +151,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
context 'when it exceeds the application limits' do
before do
- create(:ci_runner, runner_type: :group_type, groups: [group])
+ create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 1.month.ago)
create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
end
@@ -147,6 +163,23 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(group.runners.reload.size).to eq(1)
end
end
+
+ context 'when abandoned runners cause application limits to not be exceeded' do
+ before do
+ create(:ci_runner, runner_type: :group_type, groups: [group], created_at: 4.months.ago, contacted_at: 3.months.ago)
+ create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 4.months.ago)
+ create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
+ end
+
+ it 'creates runner' do
+ request
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['message']).to be_nil
+ expect(group.runners.reload.size).to eq(3)
+ expect(group.runners.recent.size).to eq(1)
+ end
+ end
end
end
diff --git a/spec/services/ci/update_build_state_service_spec.rb b/spec/services/ci/update_build_state_service_spec.rb
index 63190cc5d49..b0405226275 100644
--- a/spec/services/ci/update_build_state_service_spec.rb
+++ b/spec/services/ci/update_build_state_service_spec.rb
@@ -3,8 +3,9 @@
require 'spec_helper'
RSpec.describe Ci::UpdateBuildStateService do
- let(:project) { create(:project) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
let(:metrics) { spy('metrics') }
@@ -47,25 +48,6 @@ RSpec.describe Ci::UpdateBuildStateService do
end
end
- context 'when request payload carries a trace' do
- let(:params) { { state: 'success', trace: 'overwritten' } }
-
- it 'overwrites a trace' do
- result = subject.execute
-
- expect(build.trace.raw).to eq 'overwritten'
- expect(result.status).to eq 200
- end
-
- it 'updates overwrite operation metric' do
- execute_with_stubbed_metrics!
-
- expect(metrics)
- .to have_received(:increment_trace_operation)
- .with(operation: :overwrite)
- end
- end
-
context 'when state is unknown' do
let(:params) { { state: 'unknown' } }
diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index 339efd31534..ccc64c80fd4 100644
--- a/spec/support/shared_examples/namespaces/traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -17,6 +17,28 @@ RSpec.shared_examples 'namespace traversal' do
end
end
+ describe '#root_ancestor' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:nested_group) { create(:group, parent: group) }
+ let_it_be(:deep_nested_group) { create(:group, parent: nested_group) }
+
+ it 'returns the correct root ancestor' do
+ expect(group.root_ancestor).to eq(group)
+ expect(nested_group.root_ancestor).to eq(group)
+ expect(deep_nested_group.root_ancestor).to eq(group)
+ end
+
+ describe '#recursive_root_ancestor' do
+ let(:groups) { [group, nested_group, deep_nested_group] }
+
+ it "is equivalent to #recursive_root_ancestor" do
+ groups.each do |group|
+ expect(group.root_ancestor).to eq(group.recursive_root_ancestor)
+ end
+ end
+ end
+ end
+
describe '#self_and_hierarchy' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }