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>2020-01-10 18:07:47 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-01-10 18:07:47 +0300
commit8b1228b0d409d7751f01d9fb72ebfbbf62399486 (patch)
tree1b4126fe48d7666a90c0d7ee26230cf8379b6410 /spec
parent96b0c1245c93585a8b0fe23e22306d32ff4e4905 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js68
-rw-r--r--spec/frontend/ide/stores/mutations/file_spec.js59
-rw-r--r--spec/frontend/repository/utils/readme_spec.js43
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb70
-rw-r--r--spec/lib/api/helpers/pagination_strategies_spec.rb97
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb36
-rw-r--r--spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb79
-rw-r--r--spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb67
-rw-r--r--spec/lib/gitlab/ci/config/entry/release/assets_spec.rb69
-rw-r--r--spec/lib/gitlab/ci/config/entry/release_spec.rb114
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb53
-rw-r--r--spec/lib/gitlab/config/entry/attributable_spec.rb2
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb77
-rw-r--r--spec/lib/gitlab/pagination/keyset/page_spec.rb11
-rw-r--r--spec/lib/gitlab/pagination/keyset/pager_spec.rb54
-rw-r--r--spec/lib/gitlab/pagination/keyset/request_context_spec.rb4
-rw-r--r--spec/lib/gitlab/pagination/keyset_spec.rb16
-rw-r--r--spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb21
-rw-r--r--spec/requests/api/projects_spec.rb96
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb66
-rw-r--r--spec/services/spam_service_spec.rb2
23 files changed, 968 insertions, 186 deletions
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index 8ba7b554f43..2d72ae770ab 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -202,6 +202,53 @@ describe('IDE store file actions', () => {
};
});
+ describe('call to service', () => {
+ const callExpectation = serviceCalled => {
+ store.dispatch('getFileData', { path: localFile.path });
+
+ if (serviceCalled) {
+ expect(service.getFileData).toHaveBeenCalled();
+ } else {
+ expect(service.getFileData).not.toHaveBeenCalled();
+ }
+ };
+
+ beforeEach(() => {
+ service.getFileData.mockImplementation(() => new Promise(() => {}));
+ });
+
+ it("isn't called if file.raw exists", () => {
+ localFile.raw = 'raw data';
+
+ callExpectation(false);
+ });
+
+ it("isn't called if file is a tempFile", () => {
+ localFile.raw = '';
+ localFile.tempFile = true;
+
+ callExpectation(false);
+ });
+
+ it('is called if file is a tempFile but also renamed', () => {
+ localFile.raw = '';
+ localFile.tempFile = true;
+ localFile.prevPath = 'old_path';
+
+ callExpectation(true);
+ });
+
+ it('is called if tempFile but file was deleted and readded', () => {
+ localFile.raw = '';
+ localFile.tempFile = true;
+ localFile.prevPath = 'old_path';
+
+ store.state.stagedFiles = [{ ...localFile, deleted: true }];
+
+ callExpectation(true);
+ });
+ });
+
describe('success', () => {
beforeEach(() => {
mock.onGet(`${RELATIVE_URL_ROOT}/test/test/7297abc/${localFile.path}`).replyOnce(
@@ -332,10 +379,10 @@ describe('IDE store file actions', () => {
mock.onGet(`${RELATIVE_URL_ROOT}/test/test/7297abc/${localFile.path}`).networkError();
});
- it('dispatches error action', done => {
+ it('dispatches error action', () => {
const dispatch = jest.fn();
- actions
+ return actions
.getFileData(
{ state: store.state, commit() {}, dispatch, getters: store.getters },
{ path: localFile.path },
@@ -350,10 +397,7 @@ describe('IDE store file actions', () => {
makeFileActive: true,
},
});
-
- done();
- })
- .catch(done.fail);
+ });
});
});
});
@@ -446,12 +490,14 @@ describe('IDE store file actions', () => {
mock.onGet(/(.*)/).networkError();
});
- it('dispatches error action', done => {
+ it('dispatches error action', () => {
const dispatch = jest.fn();
- actions
- .getRawFileData({ state: store.state, commit() {}, dispatch }, { path: tmpFile.path })
- .then(done.fail)
+ return actions
+ .getRawFileData(
+ { state: store.state, commit() {}, dispatch, getters: store.getters },
+ { path: tmpFile.path },
+ )
.catch(() => {
expect(dispatch).toHaveBeenCalledWith('setErrorMessage', {
text: 'An error occurred whilst loading the file content.',
@@ -461,8 +507,6 @@ describe('IDE store file actions', () => {
path: tmpFile.path,
},
});
-
- done();
});
});
});
diff --git a/spec/frontend/ide/stores/mutations/file_spec.js b/spec/frontend/ide/stores/mutations/file_spec.js
index 8cb386d27e5..cd308ee9991 100644
--- a/spec/frontend/ide/stores/mutations/file_spec.js
+++ b/spec/frontend/ide/stores/mutations/file_spec.js
@@ -11,7 +11,7 @@ describe('IDE store file mutations', () => {
beforeEach(() => {
localStore = createStore();
localState = localStore.state;
- localFile = { ...file(), type: 'blob' };
+ localFile = { ...file('file'), type: 'blob', content: 'original' };
localState.entries[localFile.path] = localFile;
});
@@ -139,35 +139,68 @@ describe('IDE store file mutations', () => {
});
describe('SET_FILE_RAW_DATA', () => {
- it('sets raw data', () => {
+ const callMutationForFile = f => {
mutations.SET_FILE_RAW_DATA(localState, {
- file: localFile,
+ file: f,
raw: 'testing',
+ fileDeletedAndReadded: localStore.getters.isFileDeletedAndReadded(localFile.path),
});
+ };
+
+ it('sets raw data', () => {
+ callMutationForFile(localFile);
expect(localFile.raw).toBe('testing');
});
+ it('sets raw data to stagedFile if file was deleted and readded', () => {
+ localState.stagedFiles = [{ ...localFile, deleted: true }];
+ localFile.tempFile = true;
+
+ callMutationForFile(localFile);
+
+ expect(localFile.raw).toBeFalsy();
+ expect(localState.stagedFiles[0].raw).toBe('testing');
+ });
+
+ it("sets raw data to a file's content if tempFile is empty", () => {
+ localFile.tempFile = true;
+ localFile.content = '';
+
+ callMutationForFile(localFile);
+
+ expect(localFile.raw).toBeFalsy();
+ expect(localFile.content).toBe('testing');
+ });
+
it('adds raw data to open pending file', () => {
localState.openFiles.push({ ...localFile, pending: true });
- mutations.SET_FILE_RAW_DATA(localState, {
- file: localFile,
- raw: 'testing',
- });
+ callMutationForFile(localFile);
expect(localState.openFiles[0].raw).toBe('testing');
});
- it('does not add raw data to open pending tempFile file', () => {
- localState.openFiles.push({ ...localFile, pending: true, tempFile: true });
+ it('sets raw to content of a renamed tempFile', () => {
+ localFile.tempFile = true;
+ localFile.prevPath = 'old_path';
+ localState.openFiles.push({ ...localFile, pending: true });
- mutations.SET_FILE_RAW_DATA(localState, {
- file: localFile,
- raw: 'testing',
- });
+ callMutationForFile(localFile);
expect(localState.openFiles[0].raw).not.toBe('testing');
+ expect(localState.openFiles[0].content).toBe('testing');
+ });
+
+ it('adds raw data to a staged deleted file if unstaged change has a tempFile of the same name', () => {
+ localFile.tempFile = true;
+ localState.openFiles.push({ ...localFile, pending: true });
+ localState.stagedFiles = [{ ...localFile, deleted: true }];
+
+ callMutationForFile(localFile);
+
+ expect(localFile.raw).toBeFalsy();
+ expect(localState.stagedFiles[0].raw).toBe('testing');
});
});
diff --git a/spec/frontend/repository/utils/readme_spec.js b/spec/frontend/repository/utils/readme_spec.js
index 6b7876c8947..1b275de86c3 100644
--- a/spec/frontend/repository/utils/readme_spec.js
+++ b/spec/frontend/repository/utils/readme_spec.js
@@ -1,33 +1,38 @@
import { readmeFile } from '~/repository/utils/readme';
describe('readmeFile', () => {
- describe('markdown files', () => {
- it('returns markdown file', () => {
- expect(readmeFile([{ name: 'README' }, { name: 'README.md' }])).toEqual({
- name: 'README.md',
- });
+ it('prefers README with markup over plain text README', () => {
+ expect(readmeFile([{ name: 'README' }, { name: 'README.md' }])).toEqual({
+ name: 'README.md',
+ });
+ });
- expect(readmeFile([{ name: 'README' }, { name: 'index.md' }])).toEqual({
- name: 'index.md',
- });
+ it('is case insensitive', () => {
+ expect(readmeFile([{ name: 'README' }, { name: 'readme.rdoc' }])).toEqual({
+ name: 'readme.rdoc',
});
});
- describe('plain files', () => {
- it('returns plain file', () => {
- expect(readmeFile([{ name: 'README' }, { name: 'TEST.md' }])).toEqual({
- name: 'README',
- });
+ it('returns the first README found', () => {
+ expect(readmeFile([{ name: 'INDEX.adoc' }, { name: 'README.md' }])).toEqual({
+ name: 'INDEX.adoc',
+ });
+ });
- expect(readmeFile([{ name: 'readme' }, { name: 'TEST.md' }])).toEqual({
- name: 'readme',
- });
+ it('expects extension to be separated by dot', () => {
+ expect(readmeFile([{ name: 'readmeXorg' }, { name: 'index.org' }])).toEqual({
+ name: 'index.org',
});
});
- describe('non-previewable file', () => {
- it('returns undefined', () => {
- expect(readmeFile([{ name: 'index.js' }, { name: 'TEST.md' }])).toBe(undefined);
+ it('returns plain text README when there is no README with markup', () => {
+ expect(readmeFile([{ name: 'README' }, { name: 'NOT_README.md' }])).toEqual({
+ name: 'README',
});
});
+
+ it('returns undefined when there are no appropriate files', () => {
+ expect(readmeFile([{ name: 'index.js' }, { name: 'md.README' }])).toBe(undefined);
+ expect(readmeFile([])).toBe(undefined);
+ });
});
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index 2d5bec2e752..796c753d6c4 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -5,70 +5,14 @@ require 'spec_helper'
describe API::Helpers::Pagination do
subject { Class.new.include(described_class).new }
- let(:expected_result) { double("result", to_a: double) }
- let(:relation) { double("relation") }
- let(:params) { {} }
+ let(:paginator) { double('paginator') }
+ let(:relation) { double('relation') }
+ let(:expected_result) { double('expected result') }
- before do
- allow(subject).to receive(:params).and_return(params)
- end
-
- describe '#paginate' do
- let(:offset_pagination) { double("offset pagination") }
-
- it 'delegates to OffsetPagination' do
- expect(::Gitlab::Pagination::OffsetPagination).to receive(:new).with(subject).and_return(offset_pagination)
- expect(offset_pagination).to receive(:paginate).with(relation).and_return(expected_result)
-
- result = subject.paginate(relation)
-
- expect(result).to eq(expected_result)
- end
- end
-
- describe '#paginate_and_retrieve!' do
- context 'for offset pagination' do
- before do
- allow(Gitlab::Pagination::Keyset).to receive(:available?).and_return(false)
- end
-
- it 'delegates to paginate' do
- expect(subject).to receive(:paginate).with(relation).and_return(expected_result)
-
- result = subject.paginate_and_retrieve!(relation)
-
- expect(result).to eq(expected_result.to_a)
- end
- end
-
- context 'for keyset pagination' do
- let(:params) { { pagination: 'keyset' } }
- let(:request_context) { double('request context') }
-
- before do
- allow(Gitlab::Pagination::Keyset::RequestContext).to receive(:new).with(subject).and_return(request_context)
- end
-
- context 'when keyset pagination is available' do
- it 'delegates to KeysetPagination' do
- expect(Gitlab::Pagination::Keyset).to receive(:available?).and_return(true)
- expect(Gitlab::Pagination::Keyset).to receive(:paginate).with(request_context, relation).and_return(expected_result)
-
- result = subject.paginate_and_retrieve!(relation)
-
- expect(result).to eq(expected_result.to_a)
- end
- end
-
- context 'when keyset pagination is not available' do
- it 'renders a 501 error if keyset pagination isnt available yet' do
- expect(Gitlab::Pagination::Keyset).to receive(:available?).with(request_context, relation).and_return(false)
- expect(Gitlab::Pagination::Keyset).not_to receive(:paginate)
- expect(subject).to receive(:error!).with(/not yet available/, 405)
+ it 'delegates to OffsetPagination' do
+ expect(Gitlab::Pagination::OffsetPagination).to receive(:new).with(subject).and_return(paginator)
+ expect(paginator).to receive(:paginate).with(relation).and_return(expected_result)
- subject.paginate_and_retrieve!(relation)
- end
- end
- end
+ expect(subject.paginate(relation)).to eq(expected_result)
end
end
diff --git a/spec/lib/api/helpers/pagination_strategies_spec.rb b/spec/lib/api/helpers/pagination_strategies_spec.rb
new file mode 100644
index 00000000000..a418c09a824
--- /dev/null
+++ b/spec/lib/api/helpers/pagination_strategies_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Helpers::PaginationStrategies do
+ subject { Class.new.include(described_class).new }
+
+ let(:expected_result) { double("result") }
+ let(:relation) { double("relation") }
+ let(:params) { {} }
+
+ before do
+ allow(subject).to receive(:params).and_return(params)
+ end
+
+ describe '#paginate_with_strategies' do
+ let(:paginator) { double("paginator", paginate: expected_result, finalize: nil) }
+
+ before do
+ allow(subject).to receive(:paginator).with(relation).and_return(paginator)
+ end
+
+ it 'yields paginated relation' do
+ expect { |b| subject.paginate_with_strategies(relation, &b) }.to yield_with_args(expected_result)
+ end
+
+ it 'calls #finalize with first value returned from block' do
+ return_value = double
+ expect(paginator).to receive(:finalize).with(return_value)
+
+ subject.paginate_with_strategies(relation) do |records|
+ some_options = {}
+ [return_value, some_options]
+ end
+ end
+
+ it 'returns whatever the block returns' do
+ return_value = [double, double]
+
+ result = subject.paginate_with_strategies(relation) do |records|
+ return_value
+ end
+
+ expect(result).to eq(return_value)
+ end
+ end
+
+ describe '#paginator' do
+ context 'offset pagination' do
+ let(:paginator) { double("paginator") }
+
+ before do
+ allow(subject).to receive(:keyset_pagination_enabled?).and_return(false)
+ end
+
+ it 'delegates to OffsetPagination' do
+ expect(Gitlab::Pagination::OffsetPagination).to receive(:new).with(subject).and_return(paginator)
+
+ expect(subject.paginator(relation)).to eq(paginator)
+ end
+ end
+
+ context 'for keyset pagination' do
+ let(:params) { { pagination: 'keyset' } }
+ let(:request_context) { double('request context') }
+ let(:pager) { double('pager') }
+
+ before do
+ allow(subject).to receive(:keyset_pagination_enabled?).and_return(true)
+ allow(Gitlab::Pagination::Keyset::RequestContext).to receive(:new).with(subject).and_return(request_context)
+ end
+
+ context 'when keyset pagination is available' do
+ before do
+ allow(Gitlab::Pagination::Keyset).to receive(:available?).and_return(true)
+ allow(Gitlab::Pagination::Keyset::Pager).to receive(:new).with(request_context).and_return(pager)
+ end
+
+ it 'delegates to Pager' do
+ expect(subject.paginator(relation)).to eq(pager)
+ end
+ end
+
+ context 'when keyset pagination is not available' do
+ before do
+ allow(Gitlab::Pagination::Keyset).to receive(:available?).with(request_context, relation).and_return(false)
+ end
+
+ it 'renders a 501 error' do
+ expect(subject).to receive(:error!).with(/not yet available/, 405)
+
+ subject.paginator(relation)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index cc1ee63ff04..649689f7d3b 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -24,7 +24,7 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:result) do
%i[before_script script stage type after_script cache
image services only except rules needs variables artifacts
- environment coverage retry interruptible timeout tags]
+ environment coverage retry interruptible timeout release tags]
end
it { is_expected.to match_array result }
@@ -122,6 +122,21 @@ describe Gitlab::Ci::Config::Entry::Job do
it { expect(entry).to be_valid }
end
+
+ context 'when it is a release' do
+ let(:config) do
+ {
+ script: ["make changelog | tee release_changelog.txt"],
+ release: {
+ tag_name: "v0.06",
+ name: "Release $CI_TAG_NAME",
+ description: "./release_changelog.txt"
+ }
+ }
+ end
+
+ it { expect(entry).to be_valid }
+ end
end
end
@@ -443,6 +458,25 @@ describe Gitlab::Ci::Config::Entry::Job do
expect(entry.timeout).to eq('1m 1s')
end
end
+
+ context 'when it is a release' do
+ context 'when `release:description` is missing' do
+ let(:config) do
+ {
+ script: ["make changelog | tee release_changelog.txt"],
+ release: {
+ tag_name: "v0.06",
+ name: "Release $CI_TAG_NAME"
+ }
+ }
+ end
+
+ it "returns error" do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include "release description can't be blank"
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb b/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb
new file mode 100644
index 00000000000..0e346de3d9e
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Release::Assets::Link do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validation' do
+ context 'when entry config value is correct' do
+ let(:config) do
+ {
+ name: "cool-app.zip",
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.zip"
+ }
+ end
+
+ describe '#value' do
+ it 'returns link configuration' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ describe '#errors' do
+ context 'when name is not a string' do
+ let(:config) { { name: 123, url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.zip" } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'link name should be a string'
+ end
+ end
+
+ context 'when name is not present' do
+ let(:config) { { url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.zip" } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "link name can't be blank"
+ end
+ end
+
+ context 'when url is not addressable' do
+ let(:config) { { name: "cool-app.zip", url: "xyz" } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "link url is blocked: only allowed schemes are http, https"
+ end
+ end
+
+ context 'when url is not present' do
+ let(:config) { { name: "cool-app.zip" } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "link url can't be blank"
+ end
+ end
+
+ context 'when there is an unknown key present' do
+ let(:config) { { test: 100 } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'link config contains unknown keys: test'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb b/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb
new file mode 100644
index 00000000000..d12e8d966ab
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Release::Assets::Links do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validation' do
+ context 'when entry config value is correct' do
+ let(:config) do
+ [
+ {
+ name: "cool-app.zip",
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.zip"
+ },
+ {
+ name: "cool-app.exe",
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.exe"
+ }
+ ]
+ end
+
+ describe '#value' do
+ it 'returns links configuration' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ describe '#errors' do
+ context 'when value of link is invalid' do
+ let(:config) { { link: 'xyz' } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'links config should be a array'
+ end
+ end
+
+ context 'when value of links link is empty' do
+ let(:config) { { link: [] } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "links config should be a array"
+ end
+ end
+
+ context 'when there is an unknown key present' do
+ let(:config) { { test: 100 } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'links config should be a array'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb b/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb
new file mode 100644
index 00000000000..08ad5764eaa
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Release::Assets do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validation' do
+ context 'when entry config value is correct' do
+ let(:config) do
+ {
+ links: [
+ {
+ name: "cool-app.zip",
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.zip"
+ },
+ {
+ name: "cool-app.exe",
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.exe"
+ }
+ ]
+ }
+ end
+
+ describe '#value' do
+ it 'returns assets configuration' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ describe '#errors' do
+ context 'when value of assets is invalid' do
+ let(:config) { { links: 'xyz' } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'assets links should be an array of hashes'
+ end
+ end
+
+ context 'when value of assets:links is empty' do
+ let(:config) { { links: [] } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "assets links can't be blank"
+ end
+ end
+
+ context 'when there is an unknown key present' do
+ let(:config) { { test: 100 } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'assets config contains unknown keys: test'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/release_spec.rb b/spec/lib/gitlab/ci/config/entry/release_spec.rb
new file mode 100644
index 00000000000..500897569e9
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/release_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Release do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validation' do
+ context 'when entry config value is correct' do
+ let(:config) { { tag_name: 'v0.06', description: "./release_changelog.txt" } }
+
+ describe '#value' do
+ it 'returns release configuration' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context "when value includes 'assets' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ assets: [
+ {
+ name: "cool-app.zip",
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.zip"
+ }
+ ]
+ }
+ end
+
+ describe '#value' do
+ it 'returns release configuration' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context "when value includes 'name' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME"
+ }
+ end
+
+ describe '#value' do
+ it 'returns release configuration' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ describe '#errors' do
+ context 'when value of attribute is invalid' do
+ let(:config) { { description: 10 } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'release description should be a string'
+ end
+ end
+
+ context 'when release description is missing' do
+ let(:config) { { tag_name: 'v0.06' } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "release description can't be blank"
+ end
+ end
+
+ context 'when release tag_name is missing' do
+ let(:config) { { description: "./release_changelog.txt" } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "release tag name can't be blank"
+ end
+ end
+
+ context 'when there is an unknown key present' do
+ let(:config) { { test: 100 } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'release config contains unknown keys: test'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 43bd53b780f..95a5b8e88fb 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -27,16 +27,29 @@ describe Gitlab::Ci::Config::Entry::Root do
context 'when configuration is valid' do
context 'when top-level entries are defined' do
let(:hash) do
- { before_script: %w(ls pwd),
+ {
+ before_script: %w(ls pwd),
image: 'ruby:2.2',
default: {},
services: ['postgres:9.1', 'mysql:5.5'],
variables: { VAR: 'value' },
after_script: ['make clean'],
- stages: %w(build pages),
+ stages: %w(build pages release),
cache: { key: 'k', untracked: true, paths: ['public/'] },
rspec: { script: %w[rspec ls] },
- spinach: { before_script: [], variables: {}, script: 'spinach' } }
+ spinach: { before_script: [], variables: {}, script: 'spinach' },
+ release: {
+ stage: 'release',
+ before_script: [],
+ after_script: [],
+ script: ["make changelog | tee release_changelog.txt"],
+ release: {
+ tag_name: 'v0.06',
+ name: "Release $CI_TAG_NAME",
+ description: "./release_changelog.txt"
+ }
+ }
+ }
end
describe '#compose!' do
@@ -87,7 +100,7 @@ describe Gitlab::Ci::Config::Entry::Root do
describe '#stages_value' do
context 'when stages key defined' do
it 'returns array of stages' do
- expect(root.stages_value).to eq %w[build pages]
+ expect(root.stages_value).to eq %w[build pages release]
end
end
@@ -105,8 +118,9 @@ describe Gitlab::Ci::Config::Entry::Root do
describe '#jobs_value' do
it 'returns jobs configuration' do
- expect(root.jobs_value).to eq(
- rspec: { name: :rspec,
+ expect(root.jobs_value.keys).to eq([:rspec, :spinach, :release])
+ expect(root.jobs_value[:rspec]).to eq(
+ { name: :rspec,
script: %w[rspec ls],
before_script: %w(ls pwd),
image: { name: 'ruby:2.2' },
@@ -116,8 +130,10 @@ describe Gitlab::Ci::Config::Entry::Root do
variables: {},
ignore: false,
after_script: ['make clean'],
- only: { refs: %w[branches tags] } },
- spinach: { name: :spinach,
+ only: { refs: %w[branches tags] } }
+ )
+ expect(root.jobs_value[:spinach]).to eq(
+ { name: :spinach,
before_script: [],
script: %w[spinach],
image: { name: 'ruby:2.2' },
@@ -129,6 +145,20 @@ describe Gitlab::Ci::Config::Entry::Root do
after_script: ['make clean'],
only: { refs: %w[branches tags] } }
)
+ expect(root.jobs_value[:release]).to eq(
+ { name: :release,
+ stage: 'release',
+ before_script: [],
+ script: ["make changelog | tee release_changelog.txt"],
+ release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" },
+ image: { name: "ruby:2.2" },
+ services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
+ cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push" },
+ only: { refs: %w(branches tags) },
+ variables: {},
+ after_script: [],
+ ignore: false }
+ )
end
end
end
@@ -261,7 +291,7 @@ describe Gitlab::Ci::Config::Entry::Root do
# despite the fact, that key is present. See issue #18775 for more
# details.
#
- context 'when entires specified but not defined' do
+ context 'when entries are specified but not defined' do
before do
root.compose!
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 2e470d59345..9dea74f6345 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1285,6 +1285,59 @@ module Gitlab
end
end
+ describe "release" do
+ let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
+ let(:config) do
+ {
+ stages: ["build", "test", "release"], # rubocop:disable Style/WordArray
+ release: {
+ stage: "release",
+ only: ["tags"],
+ script: ["make changelog | tee release_changelog.txt"],
+ release: {
+ tag_name: "$CI_COMMIT_TAG",
+ name: "Release $CI_TAG_NAME",
+ description: "./release_changelog.txt",
+ assets: {
+ links: [
+ {
+ name: "cool-app.zip",
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.zip"
+ },
+ {
+ name: "cool-app.exe",
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.exe"
+ }
+ ]
+ }
+ }
+ }
+ }
+ end
+
+ context 'with feature flag active' do
+ before do
+ stub_feature_flags(ci_release_generation: true)
+ end
+
+ it "returns release info" do
+ expect(processor.stage_builds_attributes('release').first[:options])
+ .to eq(config[:release].except(:stage, :only))
+ end
+ end
+
+ context 'with feature flag inactive' do
+ before do
+ stub_feature_flags(ci_release_generation: false)
+ end
+
+ it "returns release info" do
+ expect(processor.stage_builds_attributes('release').first[:options].include?(config[:release]))
+ .to be false
+ end
+ end
+ end
+
describe '#environment' do
let(:config) do
{
diff --git a/spec/lib/gitlab/config/entry/attributable_spec.rb b/spec/lib/gitlab/config/entry/attributable_spec.rb
index 6b548d5c4a8..bc29a194181 100644
--- a/spec/lib/gitlab/config/entry/attributable_spec.rb
+++ b/spec/lib/gitlab/config/entry/attributable_spec.rb
@@ -59,7 +59,7 @@ describe Gitlab::Config::Entry::Attributable do
end
end
- expectation.to raise_error(ArgumentError, 'Method already defined!')
+ expectation.to raise_error(ArgumentError, 'Method already defined: length')
end
end
end
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index 474240cf620..9b29046fce9 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -53,30 +53,46 @@ describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
allow(Feature).to receive(:persisted?).with(feature_flag).and_return(false)
end
- it 'returns true when gitaly matches disk' do
- expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+ context 'when running puma with multiple threads' do
+ before do
+ allow(subject).to receive(:running_puma_with_multiple_threads?).and_return(true)
+ end
+
+ it 'returns false' do
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be false
+ end
end
- it 'returns false when disk access fails' do
- allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return("/fake/path/doesnt/exist")
+ context 'when not running puma with multiple threads' do
+ before do
+ allow(subject).to receive(:running_puma_with_multiple_threads?).and_return(false)
+ end
- expect(subject.use_rugged?(repository, feature_flag_name)).to be false
- end
+ it 'returns true when gitaly matches disk' do
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+ end
- it "returns false when gitaly doesn't match disk" do
- allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return(temp_gitaly_metadata_file)
+ it 'returns false when disk access fails' do
+ allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return("/fake/path/doesnt/exist")
- expect(subject.use_rugged?(repository, feature_flag_name)).to be_falsey
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be false
+ end
- File.delete(temp_gitaly_metadata_file)
- end
+ it "returns false when gitaly doesn't match disk" do
+ allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return(temp_gitaly_metadata_file)
- it "doesn't lead to a second rpc call because gitaly client should use the cached value" do
- expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be_falsey
- expect(Gitlab::GitalyClient).not_to receive(:filesystem_id)
+ File.delete(temp_gitaly_metadata_file)
+ end
- subject.use_rugged?(repository, feature_flag_name)
+ it "doesn't lead to a second rpc call because gitaly client should use the cached value" do
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+
+ expect(Gitlab::GitalyClient).not_to receive(:filesystem_id)
+
+ subject.use_rugged?(repository, feature_flag_name)
+ end
end
end
@@ -99,6 +115,37 @@ describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
end
end
+ describe '#running_puma_with_multiple_threads?' do
+ context 'when using Puma' do
+ before do
+ stub_const('::Puma', class_double('Puma'))
+ allow(Gitlab::Runtime).to receive(:puma?).and_return(true)
+ end
+
+ it 'returns false for single thread Puma' do
+ allow(::Puma).to receive_message_chain(:cli_config, :options).and_return(max_threads: 1)
+
+ expect(subject.running_puma_with_multiple_threads?).to be false
+ end
+
+ it 'returns true for multi-threaded Puma' do
+ allow(::Puma).to receive_message_chain(:cli_config, :options).and_return(max_threads: 2)
+
+ expect(subject.running_puma_with_multiple_threads?).to be true
+ end
+ end
+
+ context 'when not using Puma' do
+ before do
+ allow(Gitlab::Runtime).to receive(:puma?).and_return(false)
+ end
+
+ it 'returns false' do
+ expect(subject.running_puma_with_multiple_threads?).to be false
+ end
+ end
+ end
+
def create_temporary_gitaly_metadata_file
tmp = Tempfile.new('.gitaly-metadata')
gitaly_metadata = {
diff --git a/spec/lib/gitlab/pagination/keyset/page_spec.rb b/spec/lib/gitlab/pagination/keyset/page_spec.rb
index 5c03224c05a..c5ca27231d8 100644
--- a/spec/lib/gitlab/pagination/keyset/page_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/page_spec.rb
@@ -30,16 +30,14 @@ describe Gitlab::Pagination::Keyset::Page do
end
describe '#next' do
- let(:page) { described_class.new(order_by: order_by, lower_bounds: lower_bounds, per_page: per_page, end_reached: end_reached) }
- subject { page.next(new_lower_bounds, new_end_reached) }
+ let(:page) { described_class.new(order_by: order_by, lower_bounds: lower_bounds, per_page: per_page) }
+ subject { page.next(new_lower_bounds) }
let(:order_by) { { id: :desc } }
let(:lower_bounds) { { id: 42 } }
let(:per_page) { 10 }
- let(:end_reached) { false }
let(:new_lower_bounds) { { id: 21 } }
- let(:new_end_reached) { true }
it 'copies over order_by' do
expect(subject.order_by).to eq(page.order_by)
@@ -57,10 +55,5 @@ describe Gitlab::Pagination::Keyset::Page do
expect(subject.lower_bounds).to eq(new_lower_bounds)
expect(page.lower_bounds).to eq(lower_bounds)
end
-
- it 'sets end_reached only on new instance' do
- expect(subject.end_reached?).to eq(new_end_reached)
- expect(page.end_reached?).to eq(end_reached)
- end
end
end
diff --git a/spec/lib/gitlab/pagination/keyset/pager_spec.rb b/spec/lib/gitlab/pagination/keyset/pager_spec.rb
index 6d23fe2adcc..3ad1bee7225 100644
--- a/spec/lib/gitlab/pagination/keyset/pager_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/pager_spec.rb
@@ -15,15 +15,37 @@ describe Gitlab::Pagination::Keyset::Pager do
describe '#paginate' do
subject { described_class.new(request).paginate(relation) }
- it 'loads the result relation only once' do
+ it 'does not execute a query' do
expect do
subject
- end.not_to exceed_query_limit(1)
+ end.not_to exceed_query_limit(0)
end
+ it 'applies a LIMIT' do
+ expect(subject.limit_value).to eq(page.per_page)
+ end
+
+ it 'returns the limited relation' do
+ expect(subject).to eq(relation.limit(page.per_page))
+ end
+
+ context 'validating the order clause' do
+ let(:page) { Gitlab::Pagination::Keyset::Page.new(order_by: { created_at: :asc }, per_page: 3) }
+
+ it 'raises an error if has a different order clause than the page' do
+ expect { subject }.to raise_error(ArgumentError, /order_by does not match/)
+ end
+ end
+ end
+
+ describe '#finalize' do
+ let(:records) { relation.limit(page.per_page).load }
+
+ subject { described_class.new(request).finalize(records) }
+
it 'passes information about next page to request' do
- lower_bounds = relation.limit(page.per_page).last.slice(:id)
- expect(page).to receive(:next).with(lower_bounds, false).and_return(next_page)
+ lower_bounds = records.last.slice(:id)
+ expect(page).to receive(:next).with(lower_bounds).and_return(next_page)
expect(request).to receive(:apply_headers).with(next_page)
subject
@@ -32,10 +54,10 @@ describe Gitlab::Pagination::Keyset::Pager do
context 'when retrieving the last page' do
let(:relation) { Project.where('id > ?', Project.maximum(:id) - page.per_page).order(id: :asc) }
- it 'indicates this is the last page' do
- expect(request).to receive(:apply_headers) do |next_page|
- expect(next_page.end_reached?).to be_truthy
- end
+ it 'indicates there is another (likely empty) page' do
+ lower_bounds = records.last.slice(:id)
+ expect(page).to receive(:next).with(lower_bounds).and_return(next_page)
+ expect(request).to receive(:apply_headers).with(next_page)
subject
end
@@ -45,24 +67,10 @@ describe Gitlab::Pagination::Keyset::Pager do
let(:relation) { Project.where('id > ?', Project.maximum(:id) + 1).order(id: :asc) }
it 'indicates this is the last page' do
- expect(request).to receive(:apply_headers) do |next_page|
- expect(next_page.end_reached?).to be_truthy
- end
+ expect(request).not_to receive(:apply_headers)
subject
end
end
-
- it 'returns an array with the loaded records' do
- expect(subject).to eq(relation.limit(page.per_page).to_a)
- end
-
- context 'validating the order clause' do
- let(:page) { Gitlab::Pagination::Keyset::Page.new(order_by: { created_at: :asc }, per_page: 3) }
-
- it 'raises an error if has a different order clause than the page' do
- expect { subject }.to raise_error(ArgumentError, /order_by does not match/)
- end
- end
end
end
diff --git a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb
index 344ef90efa3..6cd5ccc3c19 100644
--- a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb
@@ -53,7 +53,7 @@ describe Gitlab::Pagination::Keyset::RequestContext do
let(:request) { double('request', url: "http://#{Gitlab.config.gitlab.host}/api/v4/projects?foo=bar") }
let(:params) { { foo: 'bar' } }
let(:request_context) { double('request context', params: params, request: request) }
- let(:next_page) { double('next page', order_by: { id: :asc }, lower_bounds: { id: 42 }, end_reached?: false) }
+ let(:next_page) { double('next page', order_by: { id: :asc }, lower_bounds: { id: 42 }) }
subject { described_class.new(request_context).apply_headers(next_page) }
@@ -92,7 +92,7 @@ describe Gitlab::Pagination::Keyset::RequestContext do
end
context 'with descending order' do
- let(:next_page) { double('next page', order_by: { id: :desc }, lower_bounds: { id: 42 }, end_reached?: false) }
+ let(:next_page) { double('next page', order_by: { id: :desc }, lower_bounds: { id: 42 }) }
it 'sets Links header with a link to the next page' do
orig_uri = URI.parse(request_context.request.url)
diff --git a/spec/lib/gitlab/pagination/keyset_spec.rb b/spec/lib/gitlab/pagination/keyset_spec.rb
index 5c2576d7b45..bde280c5fca 100644
--- a/spec/lib/gitlab/pagination/keyset_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset_spec.rb
@@ -3,22 +3,6 @@
require 'spec_helper'
describe Gitlab::Pagination::Keyset do
- describe '.paginate' do
- subject { described_class.paginate(request_context, relation) }
-
- let(:request_context) { double }
- let(:relation) { double }
- let(:pager) { double }
- let(:result) { double }
-
- it 'uses Pager to paginate the relation' do
- expect(Gitlab::Pagination::Keyset::Pager).to receive(:new).with(request_context).and_return(pager)
- expect(pager).to receive(:paginate).with(relation).and_return(result)
-
- expect(subject).to eq(result)
- end
- end
-
describe '.available?' do
subject { described_class }
diff --git a/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb b/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb
new file mode 100644
index 00000000000..1b0e6e140ca
--- /dev/null
+++ b/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20191128162854_drop_project_ci_cd_settings_merge_trains_enabled.rb')
+
+describe DropProjectCiCdSettingsMergeTrainsEnabled, :migration do
+ let!(:project_ci_cd_setting) { table(:project_ci_cd_settings) }
+
+ it 'correctly migrates up and down' do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(project_ci_cd_setting.column_names).to include("merge_trains_enabled")
+ }
+
+ migration.after -> {
+ project_ci_cd_setting.reset_column_information
+ expect(project_ci_cd_setting.column_names).not_to include("merge_trains_enabled")
+ }
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index f2d2cdba480..e1297c035b5 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -570,6 +570,102 @@ describe API::Projects do
let(:projects) { Project.all }
end
end
+
+ context 'with keyset pagination' do
+ let(:current_user) { user }
+ let(:projects) { [public_project, project, project2, project3] }
+
+ context 'headers and records' do
+ let(:params) { { pagination: 'keyset', order_by: :id, sort: :asc, per_page: 1 } }
+
+ it 'includes a pagination header with link to the next page' do
+ get api('/projects', current_user), params: params
+
+ expect(response.header).to include('Links')
+ expect(response.header['Links']).to include('pagination=keyset')
+ expect(response.header['Links']).to include("id_after=#{public_project.id}")
+ end
+
+ it 'contains only the first project with per_page = 1' do
+ get api('/projects', current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
+ end
+
+ it 'still includes a link if the end has reached and there is no more data after this page' do
+ get api('/projects', current_user), params: params.merge(id_after: project2.id)
+
+ expect(response.header).to include('Links')
+ expect(response.header['Links']).to include('pagination=keyset')
+ expect(response.header['Links']).to include("id_after=#{project3.id}")
+ end
+
+ it 'does not include a next link when the page does not have any records' do
+ get api('/projects', current_user), params: params.merge(id_after: Project.maximum(:id))
+
+ expect(response.header).not_to include('Links')
+ end
+
+ it 'returns an empty array when the page does not have any records' do
+ get api('/projects', current_user), params: params.merge(id_after: Project.maximum(:id))
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to eq([])
+ end
+
+ it 'responds with 501 if order_by is different from id' do
+ get api('/projects', current_user), params: params.merge(order_by: :created_at)
+
+ expect(response).to have_gitlab_http_status(405)
+ end
+ end
+
+ context 'with descending sorting' do
+ let(:params) { { pagination: 'keyset', order_by: :id, sort: :desc, per_page: 1 } }
+
+ it 'includes a pagination header with link to the next page' do
+ get api('/projects', current_user), params: params
+
+ expect(response.header).to include('Links')
+ expect(response.header['Links']).to include('pagination=keyset')
+ expect(response.header['Links']).to include("id_before=#{project3.id}")
+ end
+
+ it 'contains only the last project with per_page = 1' do
+ get api('/projects', current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(project3.id)
+ end
+ end
+
+ context 'retrieving the full relation' do
+ let(:params) { { pagination: 'keyset', order_by: :id, sort: :desc, per_page: 2 } }
+
+ it 'returns all projects' do
+ url = '/projects'
+ requests = 0
+ ids = []
+
+ while url && requests <= 5 # circuit breaker
+ requests += 1
+ get api(url, current_user), params: params
+
+ links = response.header['Links']
+ url = links&.match(/<[^>]+(\/projects\?[^>]+)>; rel="next"/) do |match|
+ match[1]
+ end
+
+ ids += JSON.parse(response.body).map { |p| p['id'] }
+ end
+
+ expect(ids).to contain_exactly(*projects.map(&:id))
+ end
+ end
+ end
end
describe 'POST /projects' do
diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
index f2cda999932..e03d87e9d49 100644
--- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -34,7 +34,7 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
it { is_expected.to be_truthy }
- context 'when the head piipeline succeeded' do
+ context 'when the head pipeline succeeded' do
let(:pipeline_status) { :success }
it { is_expected.to be_falsy }
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index bdf4dcc3142..2876f7b5f59 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -940,7 +940,7 @@ describe Ci::CreatePipelineService do
expect(resource_group.resources.first.build).to eq(nil)
end
- context 'when resourc group key includes predefined variables' do
+ context 'when resource group key includes predefined variables' do
let(:resource_group_key) { '$CI_COMMIT_REF_NAME-$CI_JOB_NAME' }
it 'interpolates the variables into the key correctly' do
@@ -969,6 +969,70 @@ describe Ci::CreatePipelineService do
end
end
+ context 'with release' do
+ shared_examples_for 'a successful release pipeline' do
+ before do
+ stub_feature_flags(ci_release_generation: true)
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ it 'is valid config' do
+ pipeline = execute_service
+ build = pipeline.builds.first
+ expect(pipeline).to be_kind_of(Ci::Pipeline)
+ expect(pipeline).to be_valid
+ expect(pipeline.yaml_errors).not_to be_present
+ expect(pipeline).to be_persisted
+ expect(build).to be_kind_of(Ci::Build)
+ expect(build.options).to eq(config[:release].except(:stage, :only).with_indifferent_access)
+ end
+ end
+
+ context 'simple example' do
+ it_behaves_like 'a successful release pipeline' do
+ let(:config) do
+ {
+ release: {
+ script: ["make changelog | tee release_changelog.txt"],
+ release: {
+ tag_name: "v0.06",
+ description: "./release_changelog.txt"
+ }
+ }
+ }
+ end
+ end
+ end
+
+ context 'example with all release metadata' do
+ it_behaves_like 'a successful release pipeline' do
+ let(:config) do
+ {
+ release: {
+ script: ["make changelog | tee release_changelog.txt"],
+ release: {
+ name: "Release $CI_TAG_NAME",
+ tag_name: "v0.06",
+ description: "./release_changelog.txt",
+ assets: {
+ links: [
+ {
+ name: "cool-app.zip",
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.zip"
+ },
+ {
+ url: "http://my.awesome.download.site/1.0-$CI_COMMIT_SHORT_SHA.exe"
+ }
+ ]
+ }
+ }
+ }
+ }
+ end
+ end
+ end
+ end
+
shared_examples 'when ref is protected' do
let(:user) { create(:user) }
diff --git a/spec/services/spam_service_spec.rb b/spec/services/spam_service_spec.rb
index 76f77583612..094684296b8 100644
--- a/spec/services/spam_service_spec.rb
+++ b/spec/services/spam_service_spec.rb
@@ -45,7 +45,7 @@ describe SpamService do
context 'when indicated as spam by akismet' do
shared_examples 'akismet spam' do
- it 'doesnt check as spam when request is missing' do
+ it "doesn't check as spam when request is missing" do
check_spam(issue, nil, false)
expect(issue).not_to be_spam