summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb34
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb2
-rw-r--r--spec/features/group_variables_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb13
-rw-r--r--spec/features/project_variables_spec.rb2
-rw-r--r--spec/features/user_sees_revert_modal_spec.rb6
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/merge_request.json8
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json32
-rw-r--r--spec/fixtures/api/schemas/variable.json2
-rw-r--r--spec/frontend/ide/lib/files_spec.js17
-rw-r--r--spec/helpers/blob_helper_spec.rb13
-rw-r--r--spec/javascripts/boards/components/board_spec.js14
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js19
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js8
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js82
-rw-r--r--spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js167
-rw-r--r--spec/javascripts/sidebar/todo_spec.js6
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js32
-rw-r--r--spec/lib/api/entities/job_request/image_spec.rb31
-rw-r--r--spec/lib/api/entities/job_request/port_spec.rb22
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/build/port_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/config/entry/port_spec.rb173
-rw-r--r--spec/lib/gitlab/ci/config/entry/ports_spec.rb70
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb70
-rw-r--r--spec/lib/gitlab/ci/config/entry/services_spec.rb87
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb57
-rw-r--r--spec/lib/gitlab/ci/templates/templates_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb4
-rw-r--r--spec/lib/gitlab/graphql/tracing_spec.rb35
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb30
-rw-r--r--spec/models/concerns/prometheus_adapter_spec.rb53
-rw-r--r--spec/models/environment_spec.rb27
-rw-r--r--spec/models/merge_request_spec.rb8
-rw-r--r--spec/models/project_spec.rb9
-rw-r--r--spec/models/project_team_spec.rb24
-rw-r--r--spec/models/project_wiki_spec.rb1
-rw-r--r--spec/requests/api/merge_requests_spec.rb8
-rw-r--r--spec/requests/api/pipelines_spec.rb7
-rw-r--r--spec/requests/api/runner_spec.rb56
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb50
-rw-r--r--spec/services/git/tag_push_service_spec.rb14
-rw-r--r--spec/services/groups/destroy_service_spec.rb38
-rw-r--r--spec/services/notes/build_service_spec.rb36
-rw-r--r--spec/services/notes/create_service_spec.rb41
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb41
-rw-r--r--spec/support/features/variable_list_shared_examples.rb123
-rw-r--r--spec/support/shared_context/policies/project_policy_shared_context.rb4
-rw-r--r--spec/support/shared_examples/time_tracking_shared_examples.rb85
51 files changed, 1366 insertions, 406 deletions
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 36ce1119100..2dca2c3976f 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -392,7 +392,7 @@ describe Projects::EnvironmentsController do
context 'when requesting metrics as JSON' do
it 'returns a metrics JSON document' do
- get :additional_metrics, params: environment_params(format: :json)
+ additional_metrics
expect(response).to have_gitlab_http_status(204)
expect(json_response).to eq({})
@@ -412,7 +412,7 @@ describe Projects::EnvironmentsController do
end
it 'returns a metrics JSON document' do
- get :additional_metrics, params: environment_params(format: :json)
+ additional_metrics
expect(response).to be_ok
expect(json_response['success']).to be(true)
@@ -420,6 +420,32 @@ describe Projects::EnvironmentsController do
expect(json_response['last_update']).to eq(42)
end
end
+
+ context 'when only one time param is provided' do
+ context 'when :metrics_time_window feature flag is disabled' do
+ before do
+ stub_feature_flags(metrics_time_window: false)
+ expect(environment).to receive(:additional_metrics).with(no_args).and_return(nil)
+ end
+
+ it 'returns a time-window agnostic response' do
+ additional_metrics(start: '1552647300.651094')
+
+ expect(response).to have_gitlab_http_status(204)
+ expect(json_response).to eq({})
+ end
+ end
+
+ it 'raises an error when start is missing' do
+ expect { additional_metrics(start: '1552647300.651094') }
+ .to raise_error(ActionController::ParameterMissing)
+ end
+
+ it 'raises an error when end is missing' do
+ expect { additional_metrics(start: '1552647300.651094') }
+ .to raise_error(ActionController::ParameterMissing)
+ end
+ end
end
describe 'GET #search' do
@@ -500,4 +526,8 @@ describe Projects::EnvironmentsController do
project_id: project,
id: environment.id)
end
+
+ def additional_metrics(opts = {})
+ get :additional_metrics, params: environment_params(format: :json, **opts)
+ end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index ece8532cb84..b64ae552efc 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -28,6 +28,8 @@ describe Projects::PipelinesController do
end
it 'returns serialized pipelines', :request_store do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
queries = ActiveRecord::QueryRecorder.new do
get_pipelines_index_json
end
diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb
index 1a53e7c9512..fc5777e8c7c 100644
--- a/spec/features/group_variables_spec.rb
+++ b/spec/features/group_variables_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Group variables', :js do
let(:user) { create(:user) }
let(:group) { create(:group) }
- let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', group: group) }
+ let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', masked: true, group: group) }
let(:page_path) { group_settings_ci_cd_path(group) }
before do
diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb
index dc0862be6fc..e5770905dbd 100644
--- a/spec/features/merge_request/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -67,18 +67,7 @@ describe 'Merge request > User posts notes', :js do
end
end
- describe 'when reply_to_individual_notes feature flag is disabled' do
- before do
- stub_feature_flags(reply_to_individual_notes: false)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'does not show a reply button' do
- expect(page).to have_no_selector('.js-reply-button')
- end
- end
-
- describe 'when reply_to_individual_notes feature flag is not set' do
+ describe 'reply button' do
before do
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb
index 6bdf5df1036..76abc640077 100644
--- a/spec/features/project_variables_spec.rb
+++ b/spec/features/project_variables_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Project variables', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value') }
+ let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value', masked: true) }
let(:page_path) { project_settings_ci_cd_path(project) }
before do
diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb
index 3b48ea4786d..d2cdade88d1 100644
--- a/spec/features/user_sees_revert_modal_spec.rb
+++ b/spec/features/user_sees_revert_modal_spec.rb
@@ -17,12 +17,14 @@ describe 'Merge request > User sees revert modal', :js do
end
it 'shows the revert modal' do
- expect(page).to have_content('Revert this merge request')
+ page.within('.modal-header') do
+ expect(page).to have_content 'Revert this merge request'
+ end
end
it 'closes the revert modal with escape keypress' do
find('#modal-revert-commit').send_keys(:escape)
- expect(page).not_to have_content('Revert this merge request')
+ expect(page).not_to have_selector('#modal-revert-commit', visible: true)
end
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_request.json b/spec/fixtures/api/schemas/public_api/v4/merge_request.json
index cd50be00418..918f2c4b47d 100644
--- a/spec/fixtures/api/schemas/public_api/v4/merge_request.json
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_request.json
@@ -119,6 +119,12 @@
"merge_status", "sha", "merge_commit_sha", "user_notes_count",
"should_remove_source_branch", "force_remove_source_branch",
"web_url", "squash"
- ]
+ ],
+ "head_pipeline": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "pipeline/detail.json" }
+ ]
+ }
}
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json
index 56f86856dd4..a7207d2d991 100644
--- a/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json
+++ b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json
@@ -13,6 +13,5 @@
"ref": { "type": "string" },
"status": { "type": "string" },
"web_url": { "type": "string" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json b/spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json
new file mode 100644
index 00000000000..63e130d4055
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json
@@ -0,0 +1,32 @@
+{
+ "type": "object",
+ "allOf": [
+ { "$ref": "basic.json" },
+ {
+ "properties": {
+ "before_sha": { "type": ["string", "null"] },
+ "tag": { "type": ["boolean"] },
+ "yaml_errors": { "type": ["string", "null"] },
+ "user": {
+ "anyOf": [
+ { "type": ["object", "null"] },
+ { "$ref": "../user/basic.json" }
+ ]
+ },
+ "created_at": { "type": ["date", "null"] },
+ "updated_at": { "type": ["date", "null"] },
+ "started_at": { "type": ["date", "null"] },
+ "finished_at": { "type": ["date", "null"] },
+ "committed_at": { "type": ["date", "null"] },
+ "duration": { "type": ["number", "null"] },
+ "coverage": { "type": ["string", "null"] },
+ "detailed_status": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "../../../status/ci_detailed_status.json" }
+ ]
+ }
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/api/schemas/variable.json b/spec/fixtures/api/schemas/variable.json
index 6f6b044115b..305071a6b3f 100644
--- a/spec/fixtures/api/schemas/variable.json
+++ b/spec/fixtures/api/schemas/variable.json
@@ -4,12 +4,14 @@
"id",
"key",
"value",
+ "masked",
"protected"
],
"properties": {
"id": { "type": "integer" },
"key": { "type": "string" },
"value": { "type": "string" },
+ "masked": { "type": "boolean" },
"protected": { "type": "boolean" },
"environment_scope": { "type": "string", "optional": true }
},
diff --git a/spec/frontend/ide/lib/files_spec.js b/spec/frontend/ide/lib/files_spec.js
index fe791aa2b74..aa1fa0373db 100644
--- a/spec/frontend/ide/lib/files_spec.js
+++ b/spec/frontend/ide/lib/files_spec.js
@@ -1,5 +1,5 @@
import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
-import { decorateFiles, splitParent } from '~/ide/lib/files';
+import { decorateFiles, splitParent, escapeFileUrl } from '~/ide/lib/files';
import { decorateData } from '~/ide/stores/utils';
const TEST_BRANCH_ID = 'lorem-ipsum';
@@ -20,7 +20,7 @@ const createEntries = paths => {
id: path,
name,
path,
- url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${path}`),
+ url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${escapeFileUrl(path)}`),
type,
previewMode: viewerInformationForPath(path),
parentPath: parent,
@@ -28,7 +28,7 @@ const createEntries = paths => {
? parentEntry.url
: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}`),
}),
- tree: children.map(childName => jasmine.objectContaining({ name: childName })),
+ tree: children.map(childName => expect.objectContaining({ name: childName })),
};
return acc;
@@ -36,10 +36,10 @@ const createEntries = paths => {
const entries = paths.reduce(createEntry, {});
- // Wrap entries in jasmine.objectContaining.
+ // Wrap entries in expect.objectContaining.
// We couldn't do this earlier because we still need to select properties from parent entries.
return Object.keys(entries).reduce((acc, key) => {
- acc[key] = jasmine.objectContaining(entries[key]);
+ acc[key] = expect.objectContaining(entries[key]);
return acc;
}, {});
@@ -47,13 +47,14 @@ const createEntries = paths => {
describe('IDE lib decorate files', () => {
it('creates entries and treeList', () => {
- const data = ['app/assets/apples/foo.js', 'app/bugs.js', 'README.md'];
+ const data = ['app/assets/apples/foo.js', 'app/bugs.js', 'app/#weird#file?.txt', 'README.md'];
const expectedEntries = createEntries([
- { path: 'app', type: 'tree', children: ['assets', 'bugs.js'] },
+ { path: 'app', type: 'tree', children: ['assets', '#weird#file?.txt', 'bugs.js'] },
{ path: 'app/assets', type: 'tree', children: ['apples'] },
{ path: 'app/assets/apples', type: 'tree', children: ['foo.js'] },
{ path: 'app/assets/apples/foo.js', type: 'blob', children: [] },
{ path: 'app/bugs.js', type: 'blob', children: [] },
+ { path: 'app/#weird#file?.txt', type: 'blob', children: [] },
{ path: 'README.md', type: 'blob', children: [] },
]);
@@ -64,7 +65,7 @@ describe('IDE lib decorate files', () => {
});
// Here we test the keys and then each key/value individually because `expect(entries).toEqual(expectedEntries)`
- // was taking a very long time for some reason. Probably due to large objects and nested `jasmine.objectContaining`.
+ // was taking a very long time for some reason. Probably due to large objects and nested `expect.objectContaining`.
const entryKeys = Object.keys(entries);
expect(entryKeys).toEqual(Object.keys(expectedEntries));
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 2bc3933809f..6808ed86c9a 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -230,5 +230,18 @@ describe BlobHelper do
expect(helper.ide_edit_path(project, "master", "")).to eq("/gitlab/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master")
end
+
+ it 'escapes special characters' do
+ Rails.application.routes.default_url_options[:script_name] = nil
+
+ expect(helper.ide_edit_path(project, "testing/#hashes", "readme.md#test")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/#hashes/-/readme.md%23test")
+ expect(helper.ide_edit_path(project, "testing/#hashes", "src#/readme.md#test")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/#hashes/-/src%23/readme.md%23test")
+ end
+
+ it 'does not escape "/" character' do
+ Rails.application.routes.default_url_options[:script_name] = nil
+
+ expect(helper.ide_edit_path(project, "testing/slashes", "readme.md/")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/slashes/-/readme.md/")
+ end
end
end
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
index 6e6b3e6950b..d08ee41802b 100644
--- a/spec/javascripts/boards/components/board_spec.js
+++ b/spec/javascripts/boards/components/board_spec.js
@@ -103,4 +103,18 @@ describe('Board component', () => {
})
.catch(done.fail);
});
+
+ it('does render add issue button', () => {
+ expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull();
+ });
+
+ it('does not render add issue button when list type is blank', done => {
+ vm.list.type = 'blank';
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.issue-count-badge-add-button')).toBeNull();
+
+ done();
+ });
+ });
});
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 70f49469300..394e60fc22c 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -127,20 +127,25 @@ describe('VariableList', () => {
variableList.init();
});
- it('should add another row when editing the last rows protected checkbox', done => {
+ it('should not add another row when editing the last rows protected checkbox', done => {
const $row = $wrapper.find('.js-row:last-child');
$row.find('.ci-variable-protected-item .js-project-feature-toggle').click();
getSetTimeoutPromise()
.then(() => {
- expect($wrapper.find('.js-row').length).toBe(2);
+ expect($wrapper.find('.js-row').length).toBe(1);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
- // Check for the correct default in the new row
- const $protectedInput = $wrapper
- .find('.js-row:last-child')
- .find('.js-ci-variable-input-protected');
+ it('should not add another row when editing the last rows masked checkbox', done => {
+ const $row = $wrapper.find('.js-row:last-child');
+ $row.find('.ci-variable-masked-item .js-project-feature-toggle').click();
- expect($protectedInput.val()).toBe('false');
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find('.js-row').length).toBe(1);
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index d9b298e84da..ef4589ada48 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -141,18 +141,16 @@ describe('DiffFile', () => {
it('should have too large warning and blob link', done => {
const BLOB_LINK = '/file/view/path';
vm.file.viewer.error = diffViewerErrors.too_large;
+ vm.file.viewer.error_message =
+ 'This source diff could not be displayed because it is too large';
vm.file.view_path = BLOB_LINK;
+ vm.file.renderIt = true;
vm.$nextTick(() => {
expect(vm.$el.innerText).toContain(
'This source diff could not be displayed because it is too large',
);
- expect(vm.$el.querySelector('.js-too-large-diff')).toBeDefined();
- expect(
- vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK),
- ).toBeGreaterThan(-1);
-
done();
});
});
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index d604e90b529..0cfcc994234 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -128,87 +128,33 @@ describe('noteActions', () => {
});
});
- describe('with feature flag replyToIndividualNotes enabled', () => {
+ describe('for showReply = true', () => {
beforeEach(() => {
- gon.features = {
- replyToIndividualNotes: true,
- };
- });
-
- afterEach(() => {
- gon.features = {};
- });
-
- describe('for showReply = true', () => {
- beforeEach(() => {
- wrapper = shallowMountNoteActions({
- ...props,
- showReply: true,
- });
- });
-
- it('shows a reply button', () => {
- const replyButton = wrapper.find({ ref: 'replyButton' });
-
- expect(replyButton.exists()).toBe(true);
+ wrapper = shallowMountNoteActions({
+ ...props,
+ showReply: true,
});
});
- describe('for showReply = false', () => {
- beforeEach(() => {
- wrapper = shallowMountNoteActions({
- ...props,
- showReply: false,
- });
- });
-
- it('does not show a reply button', () => {
- const replyButton = wrapper.find({ ref: 'replyButton' });
+ it('shows a reply button', () => {
+ const replyButton = wrapper.find({ ref: 'replyButton' });
- expect(replyButton.exists()).toBe(false);
- });
+ expect(replyButton.exists()).toBe(true);
});
});
- describe('with feature flag replyToIndividualNotes disabled', () => {
+ describe('for showReply = false', () => {
beforeEach(() => {
- gon.features = {
- replyToIndividualNotes: false,
- };
- });
-
- afterEach(() => {
- gon.features = {};
- });
-
- describe('for showReply = true', () => {
- beforeEach(() => {
- wrapper = shallowMountNoteActions({
- ...props,
- showReply: true,
- });
- });
-
- it('does not show a reply button', () => {
- const replyButton = wrapper.find({ ref: 'replyButton' });
-
- expect(replyButton.exists()).toBe(false);
+ wrapper = shallowMountNoteActions({
+ ...props,
+ showReply: false,
});
});
- describe('for showReply = false', () => {
- beforeEach(() => {
- wrapper = shallowMountNoteActions({
- ...props,
- showReply: false,
- });
- });
-
- it('does not show a reply button', () => {
- const replyButton = wrapper.find({ ref: 'replyButton' });
+ it('does not show a reply button', () => {
+ const replyButton = wrapper.find({ ref: 'replyButton' });
- expect(replyButton.exists()).toBe(false);
- });
+ expect(replyButton.exists()).toBe(false);
});
});
});
diff --git a/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js
new file mode 100644
index 00000000000..a89952ee435
--- /dev/null
+++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js
@@ -0,0 +1,167 @@
+import $ from 'jquery';
+import GLDropdown from '~/gl_dropdown'; // eslint-disable-line no-unused-vars
+import TimezoneDropdown, {
+ formatUtcOffset,
+ formatTimezone,
+} from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown';
+
+describe('Timezone Dropdown', function() {
+ preloadFixtures('pipeline_schedules/edit.html');
+
+ let $inputEl = null;
+ let $dropdownEl = null;
+ let $wrapper = null;
+ const tzListSel = '.dropdown-content ul li a.is-active';
+
+ describe('Initialize', () => {
+ describe('with dropdown already loaded', () => {
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit.html');
+ $wrapper = $('.dropdown');
+ $inputEl = $('#schedule_cron_timezone');
+ $dropdownEl = $('.js-timezone-dropdown');
+
+ // eslint-disable-next-line no-new
+ new TimezoneDropdown({
+ $inputEl,
+ $dropdownEl,
+ });
+ });
+
+ it('can take an $inputEl in the constructor', () => {
+ const tzStr = '[UTC + 5.5] Sri Jayawardenepura';
+ const tzValue = 'Asia/Colombo';
+
+ expect($inputEl.val()).toBe('UTC');
+
+ $(`${tzListSel}:contains('${tzStr}')`, $wrapper).trigger('click');
+
+ const val = $inputEl.val();
+
+ expect(val).toBe(tzValue);
+ expect(val).not.toBe('UTC');
+ });
+
+ it('will format data array of timezones into a list of offsets', () => {
+ const data = $dropdownEl.data('data');
+ const formatted = $wrapper.find(tzListSel).text();
+
+ data.forEach(item => {
+ expect(formatted).toContain(formatTimezone(item));
+ });
+ });
+
+ it('will default the timezone to UTC', () => {
+ const tz = $inputEl.val();
+
+ expect(tz).toBe('UTC');
+ });
+ });
+
+ describe('without dropdown loaded', () => {
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit.html');
+ $wrapper = $('.dropdown');
+ $inputEl = $('#schedule_cron_timezone');
+ $dropdownEl = $('.js-timezone-dropdown');
+ });
+
+ it('will populate the list of UTC offsets after the dropdown is loaded', () => {
+ expect($wrapper.find(tzListSel).length).toEqual(0);
+
+ // eslint-disable-next-line no-new
+ new TimezoneDropdown({
+ $inputEl,
+ $dropdownEl,
+ });
+
+ expect($wrapper.find(tzListSel).length).toEqual($($dropdownEl).data('data').length);
+ });
+
+ it('will call a provided handler when a new timezone is selected', () => {
+ const onSelectTimezone = jasmine.createSpy('onSelectTimezoneMock');
+ // eslint-disable-next-line no-new
+ new TimezoneDropdown({
+ $inputEl,
+ $dropdownEl,
+ onSelectTimezone,
+ });
+
+ $wrapper
+ .find(tzListSel)
+ .first()
+ .trigger('click');
+
+ expect(onSelectTimezone).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('formatUtcOffset', () => {
+ it('will convert negative utc offsets in seconds to hours and minutes', () => {
+ expect(formatUtcOffset(-21600)).toEqual('- 6');
+ });
+
+ it('will convert positive utc offsets in seconds to hours and minutes', () => {
+ expect(formatUtcOffset(25200)).toEqual('+ 7');
+ expect(formatUtcOffset(49500)).toEqual('+ 13.75');
+ });
+
+ it('will return 0 when given a string', () => {
+ expect(formatUtcOffset('BLAH')).toEqual('0');
+ expect(formatUtcOffset('$%$%')).toEqual('0');
+ });
+
+ it('will return 0 when given an array', () => {
+ expect(formatUtcOffset(['an', 'array'])).toEqual('0');
+ });
+
+ it('will return 0 when given an object', () => {
+ expect(formatUtcOffset({ some: '', object: '' })).toEqual('0');
+ });
+
+ it('will return 0 when given null', () => {
+ expect(formatUtcOffset(null)).toEqual('0');
+ });
+
+ it('will return 0 when given undefined', () => {
+ expect(formatUtcOffset(undefined)).toEqual('0');
+ });
+
+ it('will return 0 when given empty input', () => {
+ expect(formatUtcOffset('')).toEqual('0');
+ });
+ });
+
+ describe('formatTimezone', () => {
+ it('given name: "Chatham Is.", offset: "49500", will format for display as "[UTC + 13.75] Chatham Is."', () => {
+ expect(
+ formatTimezone({
+ name: 'Chatham Is.',
+ offset: 49500,
+ identifier: 'Pacific/Chatham',
+ }),
+ ).toEqual('[UTC + 13.75] Chatham Is.');
+ });
+
+ it('given name: "Saskatchewan", offset: "-21600", will format for display as "[UTC - 6] Saskatchewan"', () => {
+ expect(
+ formatTimezone({
+ name: 'Saskatchewan',
+ offset: -21600,
+ identifier: 'America/Regina',
+ }),
+ ).toEqual('[UTC - 6] Saskatchewan');
+ });
+
+ it('given name: "Accra", offset: "0", will format for display as "[UTC 0] Accra"', () => {
+ expect(
+ formatTimezone({
+ name: 'Accra',
+ offset: 0,
+ identifier: 'Africa/Accra',
+ }),
+ ).toEqual('[UTC 0] Accra');
+ });
+ });
+});
diff --git a/spec/javascripts/sidebar/todo_spec.js b/spec/javascripts/sidebar/todo_spec.js
index 657e88ecb96..f46ea5a0499 100644
--- a/spec/javascripts/sidebar/todo_spec.js
+++ b/spec/javascripts/sidebar/todo_spec.js
@@ -116,7 +116,7 @@ describe('SidebarTodo', () => {
const dataAttributes = {
issuableId: '1',
issuableType: 'epic',
- originalTitle: 'Mark todo as done',
+ originalTitle: '',
placement: 'left',
container: 'body',
boundary: 'viewport',
@@ -130,6 +130,10 @@ describe('SidebarTodo', () => {
});
});
+ it('check button label computed property', () => {
+ expect(vm.buttonLabel).toEqual('Mark todo as done');
+ });
+
it('renders button label element when `collapsed` prop is `false`', () => {
const buttonLabelEl = vm.$el.querySelector('span.issuable-todo-inner');
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 02c476f2871..cd77b0ab815 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -15,6 +15,16 @@ describe('MRWidgetHeader', () => {
gon.relative_url_root = '';
});
+ const expectDownloadDropdownItems = () => {
+ const downloadEmailPatchesEl = vm.$el.querySelector('.js-download-email-patches');
+ const downloadPlainDiffEl = vm.$el.querySelector('.js-download-plain-diff');
+
+ expect(downloadEmailPatchesEl.textContent.trim()).toEqual('Email patches');
+ expect(downloadEmailPatchesEl.getAttribute('href')).toEqual('/mr/email-patches');
+ expect(downloadPlainDiffEl.textContent.trim()).toEqual('Plain diff');
+ expect(downloadPlainDiffEl.getAttribute('href')).toEqual('/mr/plainDiffPath');
+ };
+
describe('computed', () => {
describe('shouldShowCommitsBehindText', () => {
it('return true when there are divergedCommitsCount', () => {
@@ -207,21 +217,7 @@ describe('MRWidgetHeader', () => {
});
it('renders download dropdown with links', () => {
- expect(vm.$el.querySelector('.js-download-email-patches').textContent.trim()).toEqual(
- 'Email patches',
- );
-
- expect(vm.$el.querySelector('.js-download-email-patches').getAttribute('href')).toEqual(
- '/mr/email-patches',
- );
-
- expect(vm.$el.querySelector('.js-download-plain-diff').textContent.trim()).toEqual(
- 'Plain diff',
- );
-
- expect(vm.$el.querySelector('.js-download-plain-diff').getAttribute('href')).toEqual(
- '/mr/plainDiffPath',
- );
+ expectDownloadDropdownItems();
});
});
@@ -250,10 +246,8 @@ describe('MRWidgetHeader', () => {
expect(button).toEqual(null);
});
- it('does not render download dropdown with links', () => {
- expect(vm.$el.querySelector('.js-download-email-patches')).toEqual(null);
-
- expect(vm.$el.querySelector('.js-download-plain-diff')).toEqual(null);
+ it('renders download dropdown with links', () => {
+ expectDownloadDropdownItems();
});
});
diff --git a/spec/lib/api/entities/job_request/image_spec.rb b/spec/lib/api/entities/job_request/image_spec.rb
new file mode 100644
index 00000000000..092c181ae9c
--- /dev/null
+++ b/spec/lib/api/entities/job_request/image_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Entities::JobRequest::Image do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }]}
+ let(:image) { double(name: 'image_name', entrypoint: ['foo'], ports: ports)}
+ let(:entity) { described_class.new(image) }
+
+ subject { entity.as_json }
+
+ it 'returns the image name' do
+ expect(subject[:name]).to eq 'image_name'
+ end
+
+ it 'returns the entrypoint' do
+ expect(subject[:entrypoint]).to eq ['foo']
+ end
+
+ it 'returns the ports' do
+ expect(subject[:ports]).to eq ports
+ end
+
+ context 'when the ports param is nil' do
+ let(:ports) { nil }
+
+ it 'does not return the ports' do
+ expect(subject[:ports]).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/api/entities/job_request/port_spec.rb b/spec/lib/api/entities/job_request/port_spec.rb
new file mode 100644
index 00000000000..40ab4cd6231
--- /dev/null
+++ b/spec/lib/api/entities/job_request/port_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::API::Entities::JobRequest::Port do
+ let(:port) { double(number: 80, protocol: 'http', name: 'name')}
+ let(:entity) { described_class.new(port) }
+
+ subject { entity.as_json }
+
+ it 'returns the port number' do
+ expect(subject[:number]).to eq 80
+ end
+
+ it 'returns if the port protocol' do
+ expect(subject[:protocol]).to eq 'http'
+ end
+
+ it 'returns the port name' do
+ expect(subject[:name]).to eq 'name'
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 773a52cdfbc..6e20e0ef5c3 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -18,11 +18,16 @@ describe Gitlab::Ci::Build::Image do
it 'populates fabricated object with the proper name attribute' do
expect(subject.name).to eq(image_name)
end
+
+ it 'does not populate the ports' do
+ expect(subject.ports).to be_empty
+ end
end
context 'when image is defined as hash' do
let(:entrypoint) { '/bin/sh' }
- let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint } } ) }
+
+ let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint, ports: [80] } } ) }
it 'fabricates an object of the proper class' do
is_expected.to be_kind_of(described_class)
@@ -32,6 +37,13 @@ describe Gitlab::Ci::Build::Image do
expect(subject.name).to eq(image_name)
expect(subject.entrypoint).to eq(entrypoint)
end
+
+ it 'populates the ports' do
+ port = subject.ports.first
+ expect(port.number).to eq 80
+ expect(port.protocol).to eq 'http'
+ expect(port.name).to eq 'default_port'
+ end
end
context 'when image name is empty' do
@@ -67,6 +79,10 @@ describe Gitlab::Ci::Build::Image do
expect(subject.first).to be_kind_of(described_class)
expect(subject.first.name).to eq(service_image_name)
end
+
+ it 'does not populate the ports' do
+ expect(subject.first.ports).to be_empty
+ end
end
context 'when service is defined as hash' do
@@ -75,7 +91,7 @@ describe Gitlab::Ci::Build::Image do
let(:service_command) { 'sleep 30' }
let(:job) do
create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint,
- alias: service_alias, command: service_command }] })
+ alias: service_alias, command: service_command, ports: [80] }] })
end
it 'fabricates an non-empty array of objects' do
@@ -89,6 +105,11 @@ describe Gitlab::Ci::Build::Image do
expect(subject.first.entrypoint).to eq(service_entrypoint)
expect(subject.first.alias).to eq(service_alias)
expect(subject.first.command).to eq(service_command)
+
+ port = subject.first.ports.first
+ expect(port.number).to eq 80
+ expect(port.protocol).to eq 'http'
+ expect(port.name).to eq 'default_port'
end
end
diff --git a/spec/lib/gitlab/ci/build/port_spec.rb b/spec/lib/gitlab/ci/build/port_spec.rb
new file mode 100644
index 00000000000..1413780dfa6
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/port_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Port do
+ subject { described_class.new(port) }
+
+ context 'when port is defined as an integer' do
+ let(:port) { 80 }
+
+ it 'populates the object' do
+ expect(subject.number).to eq 80
+ expect(subject.protocol).to eq described_class::DEFAULT_PORT_PROTOCOL
+ expect(subject.name).to eq described_class::DEFAULT_PORT_NAME
+ end
+ end
+
+ context 'when port is defined as hash' do
+ let(:port) { { number: 80, protocol: 'https', name: 'port_name' } }
+
+ it 'populates the object' do
+ expect(subject.number).to eq 80
+ expect(subject.protocol).to eq 'https'
+ expect(subject.name).to eq 'port_name'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index 1a4d9ed5517..1ebdda398b9 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -35,6 +35,12 @@ describe Gitlab::Ci::Config::Entry::Image do
expect(entry.entrypoint).to be_nil
end
end
+
+ describe '#ports' do
+ it "returns image's ports" do
+ expect(entry.ports).to be_nil
+ end
+ end
end
context 'when configuration is a hash' do
@@ -69,6 +75,38 @@ describe Gitlab::Ci::Config::Entry::Image do
expect(entry.entrypoint).to eq %w(/bin/sh run)
end
end
+
+ context 'when configuration has ports' do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+ let(:config) { { name: 'ruby:2.2', entrypoint: %w(/bin/sh run), ports: ports } }
+ let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:image_ports) { false }
+
+ context 'when with_image_ports metadata is not enabled' do
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("image config contains disallowed keys: ports")
+ end
+ end
+ end
+
+ context 'when with_image_ports metadata is enabled' do
+ let(:image_ports) { true }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#ports' do
+ it "returns image's ports" do
+ expect(entry.ports).to eq ports
+ end
+ end
+ end
+ end
end
context 'when entry value is not correct' do
@@ -76,8 +114,8 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
- expect(entry.errors)
- .to include 'image config should be a hash or a string'
+ expect(entry.errors.first)
+ .to match /config should be a hash or a string/
end
end
@@ -93,8 +131,8 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
- expect(entry.errors)
- .to include 'image config contains unknown keys: non_existing'
+ expect(entry.errors.first)
+ .to match /config contains unknown keys: non_existing/
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/port_spec.rb b/spec/lib/gitlab/ci/config/entry/port_spec.rb
new file mode 100644
index 00000000000..5f8f294334e
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/port_spec.rb
@@ -0,0 +1,173 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Port do
+ let(:entry) { described_class.new(config) }
+
+ before do
+ entry.compose!
+ end
+
+ context 'when configuration is a string' do
+ let(:config) { 80 }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid hash' do
+ expect(entry.value).to eq(number: 80)
+ end
+ end
+
+ describe '#number' do
+ it "returns port number" do
+ expect(entry.number).to eq 80
+ end
+ end
+
+ describe '#protocol' do
+ it "is nil" do
+ expect(entry.protocol).to be_nil
+ end
+ end
+
+ describe '#name' do
+ it "is nil" do
+ expect(entry.name).to be_nil
+ end
+ end
+ end
+
+ context 'when configuration is a hash' do
+ context 'with the complete hash' do
+ let(:config) do
+ { number: 80,
+ protocol: 'http',
+ name: 'foobar' }
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid hash' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#number' do
+ it "returns port number" do
+ expect(entry.number).to eq 80
+ end
+ end
+
+ describe '#protocol' do
+ it "returns port protocol" do
+ expect(entry.protocol).to eq 'http'
+ end
+ end
+
+ describe '#name' do
+ it "returns port name" do
+ expect(entry.name).to eq 'foobar'
+ end
+ end
+ end
+
+ context 'with only the port number' do
+ let(:config) { { number: 80 } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid hash' do
+ expect(entry.value).to eq(number: 80)
+ end
+ end
+
+ describe '#number' do
+ it "returns port number" do
+ expect(entry.number).to eq 80
+ end
+ end
+
+ describe '#protocol' do
+ it "is nil" do
+ expect(entry.protocol).to be_nil
+ end
+ end
+
+ describe '#name' do
+ it "is nil" do
+ expect(entry.name).to be_nil
+ end
+ end
+ end
+
+ context 'without the number' do
+ let(:config) { { protocol: 'http' } }
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+ end
+
+ context 'when configuration is invalid' do
+ let(:config) { '80' }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+
+ context 'when protocol' do
+ let(:config) { { number: 80, protocol: protocol, name: 'foobar' } }
+
+ context 'is http' do
+ let(:protocol) { 'http' }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'is https' do
+ let(:protocol) { 'https' }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'is neither http nor https' do
+ let(:protocol) { 'foo' }
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry.errors).to include("port protocol should be http or https")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/ports_spec.rb b/spec/lib/gitlab/ci/config/entry/ports_spec.rb
new file mode 100644
index 00000000000..2063bd1d86c
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/ports_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Ports do
+ let(:entry) { described_class.new(config) }
+
+ before do
+ entry.compose!
+ end
+
+ context 'when configuration is valid' do
+ let(:config) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid array' do
+ expect(entry.value).to eq(config)
+ end
+ end
+ end
+
+ context 'when configuration is invalid' do
+ let(:config) { 'postgresql:9.5' }
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ context 'when any of the ports' do
+ before do
+ expect(entry).not_to be_valid
+ expect(entry.errors.count).to eq 1
+ end
+
+ context 'have the same name' do
+ let(:config) do
+ [{ number: 80, protocol: 'http', name: 'foobar' },
+ { number: 81, protocol: 'http', name: 'foobar' }]
+ end
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry.errors.first).to match /each port name must be different/
+ end
+ end
+ end
+
+ context 'have the same port' do
+ let(:config) do
+ [{ number: 80, protocol: 'http', name: 'foobar' },
+ { number: 80, protocol: 'http', name: 'foobar1' }]
+ end
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry.errors.first).to match /each port number can only be referenced once/
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 9ebf947a751..d5bd139b5f1 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -39,6 +39,12 @@ describe Gitlab::Ci::Config::Entry::Service do
expect(entry.command).to be_nil
end
end
+
+ describe '#ports' do
+ it "returns service's ports" do
+ expect(entry.ports).to be_nil
+ end
+ end
end
context 'when configuration is a hash' do
@@ -81,6 +87,40 @@ describe Gitlab::Ci::Config::Entry::Service do
expect(entry.entrypoint).to eq %w(/bin/sh run)
end
end
+
+ context 'when configuration has ports' do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+ let(:config) do
+ { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports }
+ end
+ let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:image_ports) { false }
+
+ context 'when with_image_ports metadata is not enabled' do
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("service config contains disallowed keys: ports")
+ end
+ end
+ end
+
+ context 'when with_image_ports metadata is enabled' do
+ let(:image_ports) { true }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#ports' do
+ it "returns image's ports" do
+ expect(entry.ports).to eq ports
+ end
+ end
+ end
+ end
end
context 'when entry value is not correct' do
@@ -88,8 +128,8 @@ describe Gitlab::Ci::Config::Entry::Service do
describe '#errors' do
it 'saves errors' do
- expect(entry.errors)
- .to include 'service config should be a hash or a string'
+ expect(entry.errors.first)
+ .to match /config should be a hash or a string/
end
end
@@ -105,8 +145,8 @@ describe Gitlab::Ci::Config::Entry::Service do
describe '#errors' do
it 'saves errors' do
- expect(entry.errors)
- .to include 'service config contains unknown keys: non_existing'
+ expect(entry.errors.first)
+ .to match /config contains unknown keys: non_existing/
end
end
@@ -116,4 +156,26 @@ describe Gitlab::Ci::Config::Entry::Service do
end
end
end
+
+ context 'when service has ports' do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+ let(:config) do
+ { name: 'postgresql:9.5', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports }
+ end
+
+ it 'alias field is mandatory' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("service alias can't be blank")
+ end
+ end
+
+ context 'when service does not have ports' do
+ let(:config) do
+ { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) }
+ end
+
+ it 'alias field is optional' do
+ expect(entry).to be_valid
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb
index 7c4319aee63..d5a1316f665 100644
--- a/spec/lib/gitlab/ci/config/entry/services_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb
@@ -32,4 +32,91 @@ describe Gitlab::Ci::Config::Entry::Services do
end
end
end
+
+ context 'when configuration has ports' do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+ let(:config) { ['postgresql:9.5', { name: 'postgresql:9.1', alias: 'postgres_old', ports: ports }] }
+ let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:image_ports) { false }
+
+ context 'when with_image_ports metadata is not enabled' do
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("service config contains disallowed keys: ports")
+ end
+ end
+ end
+
+ context 'when with_image_ports metadata is enabled' do
+ let(:image_ports) { true }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid array' do
+ expect(entry.value).to eq([{ name: 'postgresql:9.5' }, { name: 'postgresql:9.1', alias: 'postgres_old', ports: ports }])
+ end
+ end
+
+ describe 'services alias' do
+ context 'when they are not unique' do
+ let(:config) do
+ ['postgresql:9.5',
+ { name: 'postgresql:9.1', alias: 'postgres_old', ports: [80] },
+ { name: 'ruby', alias: 'postgres_old', ports: [81] }]
+ end
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("services config alias must be unique in services with ports")
+ end
+ end
+ end
+
+ context 'when they are unique' do
+ let(:config) do
+ ['postgresql:9.5',
+ { name: 'postgresql:9.1', alias: 'postgres_old', ports: [80] },
+ { name: 'ruby', alias: 'ruby', ports: [81] }]
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when one of the duplicated alias is in a service without ports' do
+ let(:config) do
+ ['postgresql:9.5',
+ { name: 'postgresql:9.1', alias: 'postgres_old', ports: [80] },
+ { name: 'ruby', alias: 'postgres_old' }]
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ context 'when there are not any ports' do
+ let(:config) do
+ ['postgresql:9.5',
+ { name: 'postgresql:9.1', alias: 'postgres_old' },
+ { name: 'ruby', alias: 'postgres_old' }]
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 18f255c1ab7..00b2753c5fc 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -123,6 +123,63 @@ describe Gitlab::Ci::Config do
)
end
end
+
+ context 'when ports have been set' do
+ context 'in the main image' do
+ let(:yml) do
+ <<-EOS
+ image:
+ name: ruby:2.2
+ ports:
+ - 80
+ EOS
+ end
+
+ it 'raises an error' do
+ expect(config.errors).to include("image config contains disallowed keys: ports")
+ end
+ end
+
+ context 'in the job image' do
+ let(:yml) do
+ <<-EOS
+ image: ruby:2.2
+
+ test:
+ script: rspec
+ image:
+ name: ruby:2.2
+ ports:
+ - 80
+ EOS
+ end
+
+ it 'raises an error' do
+ expect(config.errors).to include("jobs:test:image config contains disallowed keys: ports")
+ end
+ end
+
+ context 'in the services' do
+ let(:yml) do
+ <<-EOS
+ image: ruby:2.2
+
+ test:
+ script: rspec
+ image: ruby:2.2
+ services:
+ - name: test
+ alias: test
+ ports:
+ - 80
+ EOS
+ end
+
+ it 'raises an error' do
+ expect(config.errors).to include("jobs:test:services:service config contains disallowed keys: ports")
+ end
+ end
+ end
end
context "when using 'include' directive" do
diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb
index fbbd58280a9..4e3681cd943 100644
--- a/spec/lib/gitlab/ci/templates/templates_spec.rb
+++ b/spec/lib/gitlab/ci/templates/templates_spec.rb
@@ -4,6 +4,9 @@ require 'spec_helper'
describe "CI YML Templates" do
ABSTRACT_TEMPLATES = %w[Serverless].freeze
+ # These templates depend on the presence of the `project`
+ # param to enable processing of `include:` within CI config.
+ PROJECT_DEPENDENT_TEMPLATES = %w[Auto-DevOps DAST].freeze
def self.concrete_templates
Gitlab::Template::GitlabCiYmlTemplate.all.reject do |template|
@@ -20,7 +23,10 @@ describe "CI YML Templates" do
describe 'concrete templates with CI/CD jobs' do
concrete_templates.each do |template|
it "#{template.name} template should be valid" do
- expect { Gitlab::Ci::YamlProcessor.new(template.content) }
+ # Trigger processing of included files
+ project = create(:project, :test_repo) if PROJECT_DEPENDENT_TEMPLATES.include?(template.name)
+
+ expect { Gitlab::Ci::YamlProcessor.new(template.content, project: project) }
.not_to raise_error
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 29638ef47c5..63a0d54dcfc 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1233,7 +1233,7 @@ module Gitlab
config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config)
- end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string")
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "services:service config should be a hash or a string")
end
it "returns errors if job services parameter is not an array" do
@@ -1247,7 +1247,7 @@ module Gitlab
config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
expect do
Gitlab::Ci::YamlProcessor.new(config)
- end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string")
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:services:service config should be a hash or a string")
end
it "returns error if job configuration is invalid" do
diff --git a/spec/lib/gitlab/graphql/tracing_spec.rb b/spec/lib/gitlab/graphql/tracing_spec.rb
deleted file mode 100644
index 6bae737d0f6..00000000000
--- a/spec/lib/gitlab/graphql/tracing_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Graphql::Tracing do
- let!(:graphql_duration_seconds) { double('Gitlab::Metrics::NullMetric') }
-
- before do
- allow(Gitlab::Metrics)
- .to receive(:histogram)
- .with(:graphql_duration_seconds, 'GraphQL execution time')
- .and_return(graphql_duration_seconds)
- end
-
- it 'updates graphql histogram with expected labels' do
- query = 'query { users { id } }'
-
- expect_metric('graphql.lex', 'lex')
- expect_metric('graphql.parse', 'parse')
- expect_metric('graphql.validate', 'validate')
- expect_metric('graphql.analyze', 'analyze_multiplex')
- expect_metric('graphql.execute', 'execute_query_lazy')
- expect_metric('graphql.execute', 'execute_multiplex')
-
- GitlabSchema.execute(query)
- end
-
- private
-
- def expect_metric(platform_key, key)
- expect(graphql_duration_seconds)
- .to receive(:observe)
- .with({ platform_key: platform_key, key: key }, be > 0.0)
- end
-end
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
index 5a88b23aa82..a6589f0c0a3 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
@@ -9,9 +9,35 @@ describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
let(:query_params) { [environment.id] }
it 'queries using specific time' do
- expect(client).to receive(:query_range).with(anything, start: 8.hours.ago.to_f, stop: Time.now.to_f)
-
+ expect(client).to receive(:query_range)
+ .with(anything, start: 8.hours.ago.to_f, stop: Time.now.to_f)
expect(query_result).not_to be_nil
end
+
+ context 'when start and end time parameters are provided' do
+ let(:query_params) { [environment.id, start_time, end_time] }
+
+ context 'as unix timestamps' do
+ let(:start_time) { 4.hours.ago.to_f }
+ let(:end_time) { 2.hours.ago.to_f }
+
+ it 'queries using the provided times' do
+ expect(client).to receive(:query_range)
+ .with(anything, start: start_time, stop: end_time)
+ expect(query_result).not_to be_nil
+ end
+ end
+
+ context 'as Date/Time objects' do
+ let(:start_time) { 4.hours.ago }
+ let(:end_time) { 2.hours.ago }
+
+ it 'queries using the provided times converted to unix' do
+ expect(client).to receive(:query_range)
+ .with(anything, start: start_time.to_f, stop: end_time.to_f)
+ expect(query_result).not_to be_nil
+ end
+ end
+ end
end
end
diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb
index 7148261b1e4..25a2d290f76 100644
--- a/spec/models/concerns/prometheus_adapter_spec.rb
+++ b/spec/models/concerns/prometheus_adapter_spec.rb
@@ -6,14 +6,15 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
include PrometheusHelpers
include ReactiveCachingHelpers
- class TestClass
- include PrometheusAdapter
- end
-
let(:project) { create(:prometheus_project) }
let(:service) { project.prometheus_service }
- let(:described_class) { TestClass }
+ let(:described_class) do
+ Class.new do
+ include PrometheusAdapter
+ end
+ end
+
let(:environment_query) { Gitlab::Prometheus::Queries::EnvironmentQuery }
describe '#query' do
@@ -76,6 +77,28 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
end
end
end
+
+ describe 'additional_metrics' do
+ let(:additional_metrics_environment_query) { Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery }
+ let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
+ let(:time_window) { [1552642245.067, 1552642095.831] }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ context 'with valid data' do
+ subject { service.query(:additional_metrics_environment, environment, *time_window) }
+
+ before do
+ stub_reactive_cache(service, prometheus_data, additional_metrics_environment_query, environment.id, *time_window)
+ end
+
+ it 'returns reactive data' do
+ expect(subject).to eq(prometheus_data)
+ end
+ end
+ end
end
describe '#calculate_reactive_cache' do
@@ -120,4 +143,24 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
end
end
end
+
+ describe '#build_query_args' do
+ subject { service.build_query_args(*args) }
+
+ context 'when active record models are included' do
+ let(:args) { [double(:environment, id: 12)] }
+
+ it 'serializes by id' do
+ is_expected.to eq [12]
+ end
+ end
+
+ context 'when args are safe for serialization' do
+ let(:args) { ['stringy arg', 5, 6.0, :symbolic_arg] }
+
+ it 'does nothing' do
+ is_expected.to eq args
+ end
+ end
+ end
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 448ed35cb1e..cfe7c7ef0b0 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -592,7 +592,9 @@ describe Environment do
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
it 'returns the terminals from the deployment service' do
- expect(project.deployment_platform)
+ deployment_platform_target = Gitlab.ee? ? environment : project
+
+ expect(deployment_platform_target.deployment_platform)
.to receive(:terminals).with(environment)
.and_return(:fake_terminals)
@@ -685,7 +687,8 @@ describe Environment do
describe '#additional_metrics' do
let(:project) { create(:prometheus_project) }
- subject { environment.additional_metrics }
+ let(:metric_params) { [] }
+ subject { environment.additional_metrics(*metric_params) }
context 'when the environment has additional metrics' do
before do
@@ -693,12 +696,26 @@ describe Environment do
end
it 'returns the additional metrics from the deployment service' do
- expect(environment.prometheus_adapter).to receive(:query)
- .with(:additional_metrics_environment, environment)
- .and_return(:fake_metrics)
+ expect(environment.prometheus_adapter)
+ .to receive(:query)
+ .with(:additional_metrics_environment, environment)
+ .and_return(:fake_metrics)
is_expected.to eq(:fake_metrics)
end
+
+ context 'when time window arguments are provided' do
+ let(:metric_params) { [1552642245.067, Time.now] }
+
+ it 'queries with the expected parameters' do
+ expect(environment.prometheus_adapter)
+ .to receive(:query)
+ .with(:additional_metrics_environment, environment, *metric_params.map(&:to_f))
+ .and_return(:fake_metrics)
+
+ is_expected.to eq(:fake_metrics)
+ end
+ end
end
context 'when the environment does not have metrics' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 892fdc4e4e9..6f34ef9c1bc 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -805,6 +805,14 @@ describe MergeRequest do
expect(merge_request.commits).not_to be_empty
expect(merge_request.related_notes.count).to eq(3)
end
+
+ it "excludes system notes for commits" do
+ system_note = create(:note_on_commit, :system, commit_id: merge_request.commits.first.id,
+ project: merge_request.project)
+
+ expect(merge_request.related_notes.count).to eq(2)
+ expect(merge_request.related_notes).not_to include(system_note)
+ end
end
describe '#for_fork?' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 2158d3cf3e6..33e514cd7b9 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2143,6 +2143,15 @@ describe Project do
expect(project.add_import_job).to eq(import_jid)
end
+
+ context 'without repository' do
+ it 'schedules RepositoryImportWorker' do
+ project = create(:project, import_url: generate(:url))
+
+ expect(RepositoryImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
+ expect(project.add_import_job).to eq(import_jid)
+ end
+ end
end
context 'not forked' do
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index a2d4fad9292..77c88a04cde 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -195,6 +195,30 @@ describe ProjectTeam do
end
end
+ describe '#add_users' do
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:project) { create(:project) }
+
+ it 'add the given users to the team' do
+ project.team.add_users([user1, user2], :reporter)
+
+ expect(project.team.reporter?(user1)).to be(true)
+ expect(project.team.reporter?(user2)).to be(true)
+ end
+ end
+
+ describe '#add_user' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ it 'add the given user to the team' do
+ project.team.add_user(user, :reporter)
+
+ expect(project.team.reporter?(user)).to be(true)
+ end
+ end
+
describe "#human_max_access" do
it 'returns Maintainer role' do
user = create(:user)
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 7ea67f31534..2525a6aebe0 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -1,4 +1,3 @@
-# coding: utf-8
# frozen_string_literal: true
require "spec_helper"
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 4259fda7f04..1d139200535 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -729,6 +729,14 @@ describe API::MergeRequests do
end
describe "GET /projects/:id/merge_requests/:merge_request_iid" do
+ it 'matches json schema' do
+ merge_request = create(:merge_request, :with_test_reports, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/merge_request')
+ end
+
it 'exposes known attributes' do
create(:award_emoji, :downvote, awardable: merge_request)
create(:award_emoji, :upvote, awardable: merge_request)
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 52599db9a9e..c26d31c5e0d 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -399,6 +399,13 @@ describe API::Pipelines do
describe 'GET /projects/:id/pipelines/:pipeline_id' do
context 'authorized user' do
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/pipeline/detail')
+ end
+
it 'returns project pipelines' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 3ccedd8dd06..5fdc7c64030 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -470,11 +470,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(json_response['token']).to eq(job.token)
expect(json_response['job_info']).to eq(expected_job_info)
expect(json_response['git_info']).to eq(expected_git_info)
- expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh' })
+ expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh', 'ports' => [] })
expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
- 'alias' => nil, 'command' => nil },
+ 'alias' => nil, 'command' => nil, 'ports' => [] },
{ 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh',
- 'alias' => 'docker', 'command' => 'sleep 30' }])
+ 'alias' => 'docker', 'command' => 'sleep 30', 'ports' => [] }])
expect(json_response['steps']).to eq(expected_steps)
expect(json_response['artifacts']).to eq(expected_artifacts)
expect(json_response['cache']).to eq(expected_cache)
@@ -853,6 +853,56 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
+ describe 'port support' do
+ let(:job) { create(:ci_build, pipeline: pipeline, options: options) }
+
+ context 'when job image has ports' do
+ let(:options) do
+ {
+ image: {
+ name: 'ruby',
+ ports: [80]
+ },
+ services: ['mysql']
+ }
+ end
+
+ it 'returns the image ports' do
+ request_job
+
+ expect(response).to have_http_status(:created)
+ expect(json_response).to include(
+ 'id' => job.id,
+ 'image' => a_hash_including('name' => 'ruby', 'ports' => [{ 'number' => 80, 'protocol' => 'http', 'name' => 'default_port' }]),
+ 'services' => all(a_hash_including('name' => 'mysql')))
+ end
+ end
+
+ context 'when job services settings has ports' do
+ let(:options) do
+ {
+ image: 'ruby',
+ services: [
+ {
+ name: 'tomcat',
+ ports: [{ number: 8081, protocol: 'http', name: 'custom_port' }]
+ }
+ ]
+ }
+ end
+
+ it 'returns the service ports' do
+ request_job
+
+ expect(response).to have_http_status(:created)
+ expect(json_response).to include(
+ 'id' => job.id,
+ 'image' => a_hash_including('name' => 'ruby'),
+ 'services' => all(a_hash_including('name' => 'tomcat', 'ports' => [{ 'number' => 8081, 'protocol' => 'http', 'name' => 'custom_port' }])))
+ end
+ end
+ end
+
def request_job(token = runner.token, **params)
new_params = params.merge(token: token, last_update: last_update)
post api('/jobs/request'), params: new_params, headers: { 'User-Agent' => user_agent }
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 24707cd2d41..866d709d446 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -306,6 +306,56 @@ describe Ci::CreatePipelineService do
it_behaves_like 'a failed pipeline'
end
+
+ context 'when config has ports' do
+ context 'in the main image' do
+ let(:ci_yaml) do
+ <<-EOS
+ image:
+ name: ruby:2.2
+ ports:
+ - 80
+ EOS
+ end
+
+ it_behaves_like 'a failed pipeline'
+ end
+
+ context 'in the job image' do
+ let(:ci_yaml) do
+ <<-EOS
+ image: ruby:2.2
+
+ test:
+ script: rspec
+ image:
+ name: ruby:2.2
+ ports:
+ - 80
+ EOS
+ end
+
+ it_behaves_like 'a failed pipeline'
+ end
+
+ context 'in the service' do
+ let(:ci_yaml) do
+ <<-EOS
+ image: ruby:2.2
+
+ test:
+ script: rspec
+ image: ruby:2.2
+ services:
+ - name: test
+ ports:
+ - 80
+ EOS
+ end
+
+ it_behaves_like 'a failed pipeline'
+ end
+ end
end
context 'when commit contains a [ci skip] directive' do
diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb
index e151db5827f..2d960fc9f08 100644
--- a/spec/services/git/tag_push_service_spec.rb
+++ b/spec/services/git/tag_push_service_spec.rb
@@ -31,6 +31,20 @@ describe Git::TagPushService do
end
end
+ describe 'System Hooks' do
+ let!(:push_data) { service.tap(&:execute).push_data }
+
+ it "executes system hooks after pushing a tag" do
+ expect_next_instance_of(SystemHooksService) do |system_hooks_service|
+ expect(system_hooks_service)
+ .to receive(:execute_hooks)
+ .with(push_data, :tag_push_hooks)
+ end
+
+ service.execute
+ end
+ end
+
describe "Pipelines" do
subject { service.execute }
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index d80d0f5a8a8..98114ea4919 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -82,44 +82,6 @@ describe Groups::DestroyService do
expect(Group.unscoped.count).to eq(2)
end
end
-
- context 'potential race conditions' do
- context "when the `GroupDestroyWorker` task runs immediately" do
- it "deletes the group" do
- # Commit the contents of this spec's transaction so far
- # so subsequent db connections can see it.
- #
- # DO NOT REMOVE THIS LINE, even if you see a WARNING with "No
- # transaction is currently in progress". Without this, this
- # spec will always be green, since the group created in setup
- # cannot be seen by any other connections / threads in this spec.
- Group.connection.commit_db_transaction
-
- group_record = run_with_new_database_connection do |conn|
- conn.execute("SELECT * FROM namespaces WHERE id = #{group.id}").first
- end
-
- expect(group_record).not_to be_nil
-
- # Execute the contents of `GroupDestroyWorker` in a separate thread, to
- # simulate data manipulation by the Sidekiq worker (different database
- # connection / transaction).
- expect(GroupDestroyWorker).to receive(:perform_async).and_wrap_original do |m, group_id, user_id|
- Thread.new { m[group_id, user_id] }.join(5)
- end
-
- # Kick off the initial group destroy in a new thread, so that
- # it doesn't share this spec's database transaction.
- Thread.new { described_class.new(group, user).async_execute }.join(5)
-
- group_record = run_with_new_database_connection do |conn|
- conn.execute("SELECT * FROM namespaces WHERE id = #{group.id}").first
- end
-
- expect(group_record).to be_nil
- end
- end
- end
end
describe 'synchronous delete' do
diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb
index af4daff336b..96fff20f7fb 100644
--- a/spec/services/notes/build_service_spec.rb
+++ b/spec/services/notes/build_service_spec.rb
@@ -128,37 +128,19 @@ describe Notes::BuildService do
subject { described_class.new(project, author, note: 'Test', in_reply_to_discussion_id: note.discussion_id).execute }
- shared_examples 'an individual note reply' do
- it 'builds another individual note' do
- expect(subject).to be_valid
- expect(subject).to be_a(Note)
- expect(subject.discussion_id).not_to eq(note.discussion_id)
- end
+ it 'sets the note up to be in reply to that note' do
+ expect(subject).to be_valid
+ expect(subject).to be_a(DiscussionNote)
+ expect(subject.discussion_id).to eq(note.discussion_id)
end
- context 'when reply_to_individual_notes is disabled' do
- before do
- stub_feature_flags(reply_to_individual_notes: false)
- end
-
- it_behaves_like 'an individual note reply'
- end
+ context 'when noteable does not support replies' do
+ let(:note) { create(:note_on_commit) }
- context 'when reply_to_individual_notes is enabled' do
- before do
- stub_feature_flags(reply_to_individual_notes: true)
- end
-
- it 'sets the note up to be in reply to that note' do
+ it 'builds another individual note' do
expect(subject).to be_valid
- expect(subject).to be_a(DiscussionNote)
- expect(subject.discussion_id).to eq(note.discussion_id)
- end
-
- context 'when noteable does not support replies' do
- let(:note) { create(:note_on_commit) }
-
- it_behaves_like 'an individual note reply'
+ expect(subject).to be_a(Note)
+ expect(subject.discussion_id).not_to eq(note.discussion_id)
end
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 8d8e81173ff..bcbb8950910 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -298,41 +298,20 @@ describe Notes::CreateService do
subject { described_class.new(project, user, reply_opts).execute }
- context 'when reply_to_individual_notes is disabled' do
- before do
- stub_feature_flags(reply_to_individual_notes: false)
- end
-
- it 'creates an individual note' do
- expect(subject.type).to eq(nil)
- expect(subject.discussion_id).not_to eq(existing_note.discussion_id)
- end
-
- it 'does not convert existing note' do
- expect { subject }.not_to change { existing_note.reload.type }
- end
+ it 'creates a DiscussionNote in reply to existing note' do
+ expect(subject).to be_a(DiscussionNote)
+ expect(subject.discussion_id).to eq(existing_note.discussion_id)
end
- context 'when reply_to_individual_notes is enabled' do
- before do
- stub_feature_flags(reply_to_individual_notes: true)
- end
-
- it 'creates a DiscussionNote in reply to existing note' do
- expect(subject).to be_a(DiscussionNote)
- expect(subject.discussion_id).to eq(existing_note.discussion_id)
- end
-
- it 'converts existing note to DiscussionNote' do
- expect do
- existing_note
+ it 'converts existing note to DiscussionNote' do
+ expect do
+ existing_note
- Timecop.freeze(Time.now + 1.minute) { subject }
+ Timecop.freeze(Time.now + 1.minute) { subject }
- existing_note.reload
- end.to change { existing_note.type }.from(nil).to('DiscussionNote')
- .and change { existing_note.updated_at }
- end
+ existing_note.reload
+ end.to change { existing_note.type }.from(nil).to('DiscussionNote')
+ .and change { existing_note.updated_at }
end
end
end
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 8b0f9c8ade2..c7e5cca324f 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -10,6 +10,7 @@ describe QuickActions::InterpretService do
let(:milestone) { create(:milestone, project: project, title: '9.10') }
let(:commit) { create(:commit, project: project) }
let(:inprogress) { create(:label, project: project, title: 'In Progress') }
+ let(:helmchart) { create(:label, project: project, title: 'Helm Chart Registry') }
let(:bug) { create(:label, project: project, title: 'Bug') }
let(:note) { build(:note, commit_id: merge_request.diff_head_sha) }
let(:service) { described_class.new(project, developer) }
@@ -94,6 +95,26 @@ describe QuickActions::InterpretService do
end
end
+ shared_examples 'multiword label name starting without ~' do
+ it 'fetches label ids and populates add_label_ids if content contains /label' do
+ helmchart # populate the label
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(add_label_ids: [helmchart.id])
+ end
+ end
+
+ shared_examples 'label name is included in the middle of another label name' do
+ it 'ignores the sublabel when the content contains the includer label name' do
+ helmchart # populate the label
+ create(:label, project: project, title: 'Chart')
+
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(add_label_ids: [helmchart.id])
+ end
+ end
+
shared_examples 'unlabel command' do
it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do
issuable.update!(label_ids: [inprogress.id]) # populate the label
@@ -624,6 +645,26 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
+ it_behaves_like 'multiword label name starting without ~' do
+ let(:content) { %(/label "#{helmchart.title}") }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'multiword label name starting without ~' do
+ let(:content) { %(/label "#{helmchart.title}") }
+ let(:issuable) { merge_request }
+ end
+
+ it_behaves_like 'label name is included in the middle of another label name' do
+ let(:content) { %(/label ~"#{helmchart.title}") }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'label name is included in the middle of another label name' do
+ let(:content) { %(/label ~"#{helmchart.title}") }
+ let(:issuable) { merge_request }
+ end
+
it_behaves_like 'unlabel command' do
let(:content) { %(/unlabel ~"#{inprogress.title}") }
let(:issuable) { issue }
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index 73156d18c1b..693b796fbdc 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -23,10 +23,13 @@ shared_examples 'variable list' do
end
end
- it 'adds empty variable' do
+ it 'adds a new protected variable' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('key')
- find('.js-ci-variable-input-value').set('')
+ find('.js-ci-variable-input-value').set('key_value')
+ find('.ci-variable-protected-item .js-project-feature-toggle').click
+
+ expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
end
click_button('Save variables')
@@ -37,17 +40,17 @@ shared_examples 'variable list' do
# We check the first row because it re-sorts to alphabetical order on refresh
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
expect(find('.js-ci-variable-input-key').value).to eq('key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('')
+ expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
+ expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
end
end
- it 'adds new protected variable' do
+ it 'defaults to masked' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('key')
find('.js-ci-variable-input-value').set('key_value')
- find('.ci-variable-protected-item .js-project-feature-toggle').click
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
end
click_button('Save variables')
@@ -59,7 +62,7 @@ shared_examples 'variable list' do
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
expect(find('.js-ci-variable-input-key').value).to eq('key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
end
end
@@ -163,27 +166,6 @@ shared_examples 'variable list' do
end
end
- it 'edits variable with empty value' do
- page.within('.js-ci-variable-list-section') do
- click_button('Reveal value')
-
- page.within('.js-row:nth-child(1)') do
- find('.js-ci-variable-input-key').set('new_key')
- find('.js-ci-variable-input-value').set('')
- end
-
- click_button('Save variables')
- wait_for_requests
-
- visit page_path
-
- page.within('.js-row:nth-child(1)') do
- expect(find('.js-ci-variable-input-key').value).to eq('new_key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('')
- end
- end
- end
-
it 'edits variable to be protected' do
# Create the unprotected variable
page.within('.js-ci-variable-list-section .js-row:last-child') do
@@ -251,6 +233,57 @@ shared_examples 'variable list' do
end
end
+ it 'edits variable to be unmasked' do
+ page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
+
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ visit page_path
+
+ page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ end
+ end
+
+ it 'edits variable to be masked' do
+ page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
+
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ visit page_path
+
+ page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
+
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ visit page_path
+
+ page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ end
+ end
+
it 'handles multiple edits and deletion in the middle' do
page.within('.js-ci-variable-list-section') do
# Create 2 variables
@@ -297,11 +330,11 @@ shared_examples 'variable list' do
it 'shows validation error box about duplicate keys' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('samekey')
- find('.js-ci-variable-input-value').set('value1')
+ find('.js-ci-variable-input-value').set('value123')
end
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('samekey')
- find('.js-ci-variable-input-value').set('value2')
+ find('.js-ci-variable-input-value').set('value456')
end
click_button('Save variables')
@@ -314,4 +347,34 @@ shared_examples 'variable list' do
expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables have duplicate values \(.+\)/)
end
end
+
+ it 'shows validation error box about empty values' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('empty_value')
+ find('.js-ci-variable-input-value').set('')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ page.within('.js-ci-variable-list-section') do
+ expect(all('.js-ci-variable-error-box ul li').count).to eq(1)
+ expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/)
+ end
+ end
+
+ it 'shows validation error box about unmaskable values' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('unmaskable_value')
+ find('.js-ci-variable-input-value').set('???')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ page.within('.js-ci-variable-list-section') do
+ expect(all('.js-ci-variable-error-box ul li').count).to eq(1)
+ expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/)
+ end
+ end
end
diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb
index 8bcd26ec0cd..3ad6e067674 100644
--- a/spec/support/shared_context/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_context/policies/project_policy_shared_context.rb
@@ -15,7 +15,7 @@ RSpec.shared_context 'ProjectPolicy context' do
read_project_for_iids read_issue_iid read_label
read_milestone read_project_snippet read_project_member read_note
create_project create_issue create_note upload_file create_merge_request_in
- award_emoji read_release
+ award_emoji
]
end
@@ -24,7 +24,7 @@ RSpec.shared_context 'ProjectPolicy context' do
download_code fork_project create_project_snippet update_issue
admin_issue admin_label admin_list read_commit_status read_build
read_container_image read_pipeline read_environment read_deployment
- read_merge_request download_wiki_code read_sentry_issue
+ read_merge_request download_wiki_code read_sentry_issue read_release
]
end
diff --git a/spec/support/shared_examples/time_tracking_shared_examples.rb b/spec/support/shared_examples/time_tracking_shared_examples.rb
deleted file mode 100644
index 909d4e2ee8d..00000000000
--- a/spec/support/shared_examples/time_tracking_shared_examples.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-shared_examples 'issuable time tracker' do
- it 'renders the sidebar component empty state' do
- page.within '.time-tracking-no-tracking-pane' do
- expect(page).to have_content 'No estimate or time spent'
- end
- end
-
- it 'updates the sidebar component when estimate is added' do
- submit_time('/estimate 3w 1d 1h')
-
- wait_for_requests
- page.within '.time-tracking-estimate-only-pane' do
- expect(page).to have_content '3w 1d 1h'
- end
- end
-
- it 'updates the sidebar component when spent is added' do
- submit_time('/spend 3w 1d 1h')
-
- wait_for_requests
- page.within '.time-tracking-spend-only-pane' do
- expect(page).to have_content '3w 1d 1h'
- end
- end
-
- it 'shows the comparison when estimate and spent are added' do
- submit_time('/estimate 3w 1d 1h')
- submit_time('/spend 3w 1d 1h')
-
- wait_for_requests
- page.within '.time-tracking-comparison-pane' do
- expect(page).to have_content '3w 1d 1h'
- end
- end
-
- it 'updates the sidebar component when estimate is removed' do
- submit_time('/estimate 3w 1d 1h')
- submit_time('/remove_estimate')
-
- page.within '.time-tracking-component-wrap' do
- expect(page).to have_content 'No estimate or time spent'
- end
- end
-
- it 'updates the sidebar component when spent is removed' do
- submit_time('/spend 3w 1d 1h')
- submit_time('/remove_time_spent')
-
- page.within '.time-tracking-component-wrap' do
- expect(page).to have_content 'No estimate or time spent'
- end
- end
-
- it 'shows the help state when icon is clicked' do
- page.within '.time-tracking-component-wrap' do
- find('.help-button').click
- expect(page).to have_content 'Track time with quick actions'
- expect(page).to have_content 'Learn more'
- end
- end
-
- it 'hides the help state when close icon is clicked' do
- page.within '.time-tracking-component-wrap' do
- find('.help-button').click
- find('.close-help-button').click
-
- expect(page).not_to have_content 'Track time with quick actions'
- expect(page).not_to have_content 'Learn more'
- end
- end
-
- it 'displays the correct help url' do
- page.within '.time-tracking-component-wrap' do
- find('.help-button').click
-
- expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md')
- end
- end
-end
-
-def submit_time(quick_action)
- fill_in 'note[note]', with: quick_action
- find('.js-comment-submit-button').click
- wait_for_requests
-end