summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-01-10 18:07:43 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-01-10 18:07:43 +0000
commit6f0f893bd87535b61e0ecb1ce069eaa7fcb9e5be (patch)
tree8af92b29c838e9af2fd70f9a4a2314a08f4af922 /spec
parent8b1228b0d409d7751f01d9fb72ebfbbf62399486 (diff)
downloadgitlab-ce-6f0f893bd87535b61e0ecb1ce069eaa7fcb9e5be.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/error_tracking_controller_spec.rb4
-rw-r--r--spec/db/schema_spec.rb1
-rw-r--r--spec/factories/error_tracking/detailed_error.rb1
-rw-r--r--spec/factories/releases.rb9
-rw-r--r--spec/fixtures/api/schemas/error_tracking/error_detailed.json17
-rw-r--r--spec/fixtures/lib/gitlab/import_export/complex/project.json86
-rw-r--r--spec/frontend/issue_spec.js (renamed from spec/javascripts/issue_spec.js)94
-rw-r--r--spec/frontend/lib/utils/poll_until_complete_spec.js89
-rw-r--r--spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb1
-rw-r--r--spec/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications_spec.rb75
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb36
-rw-r--r--spec/lib/sentry/client/issue_spec.rb1
-rw-r--r--spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb23
-rw-r--r--spec/migrations/patch_prometheus_services_for_shared_cluster_applications_spec.rb134
-rw-r--r--spec/models/error_tracking/project_error_tracking_setting_spec.rb47
-rw-r--r--spec/models/release_spec.rb6
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb1
-rw-r--r--spec/services/releases/update_service_spec.rb38
-rw-r--r--spec/support/migrations_helpers/prometheus_service_helpers.rb35
-rw-r--r--spec/support/shared_examples/workers/self_monitoring_shared_examples.rb28
-rw-r--r--spec/workers/self_monitoring_project_create_worker_spec.rb16
-rw-r--r--spec/workers/self_monitoring_project_delete_worker_spec.rb19
22 files changed, 682 insertions, 79 deletions
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb
index ce96eb1b9a9..65459b845f9 100644
--- a/spec/controllers/projects/error_tracking_controller_spec.rb
+++ b/spec/controllers/projects/error_tracking_controller_spec.rb
@@ -224,9 +224,11 @@ describe Projects::ErrorTrackingController do
let(:error) { build(:detailed_error_tracking_error) }
it 'returns an error' do
+ expected_error = error.as_json.except('first_release_version').merge({ 'gitlab_commit' => nil })
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('error_tracking/issue_detailed')
- expect(json_response['error']).to eq(error.as_json)
+ expect(json_response['error']).to eq(expected_error)
end
it_behaves_like 'sets the polling header'
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index a42916a83a6..17c40bec6c6 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -133,6 +133,7 @@ describe 'Database schema' do
'Ci::BuildTraceChunk' => %w[data_store],
'Ci::JobArtifact' => %w[file_type],
'Ci::Pipeline' => %w[source config_source failure_reason],
+ 'Ci::Processable' => %w[failure_reason],
'Ci::Runner' => %w[access_level],
'Ci::Stage' => %w[status],
'Clusters::Applications::Ingress' => %w[ingress_type],
diff --git a/spec/factories/error_tracking/detailed_error.rb b/spec/factories/error_tracking/detailed_error.rb
index 0fba79b2acb..7e217246f91 100644
--- a/spec/factories/error_tracking/detailed_error.rb
+++ b/spec/factories/error_tracking/detailed_error.rb
@@ -34,6 +34,7 @@ FactoryBot.define do
last_release_last_commit { '9ad419c86' }
first_release_short_version { 'abc123' }
last_release_short_version { 'abc123' }
+ first_release_version { '123456' }
skip_create
end
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index 182ee2378d4..0e79f2e6d3a 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -20,5 +20,14 @@ FactoryBot.define do
create(:evidence, release: release)
end
end
+
+ trait :with_milestones do
+ transient do
+ milestones_count { 2 }
+ end
+ after(:create) do |release, evaluator|
+ create_list(:milestone, evaluator.milestones_count, project: evaluator.project, releases: [release])
+ end
+ end
end
end
diff --git a/spec/fixtures/api/schemas/error_tracking/error_detailed.json b/spec/fixtures/api/schemas/error_tracking/error_detailed.json
index 9a6797bf3c7..57cf5e677d6 100644
--- a/spec/fixtures/api/schemas/error_tracking/error_detailed.json
+++ b/spec/fixtures/api/schemas/error_tracking/error_detailed.json
@@ -1,6 +1,6 @@
{
"type": "object",
- "required" : [
+ "required": [
"external_url",
"external_base_url",
"last_seen",
@@ -18,9 +18,10 @@
"first_release_last_commit",
"last_release_last_commit",
"first_release_short_version",
- "last_release_short_version"
+ "last_release_short_version",
+ "gitlab_commit"
],
- "properties" : {
+ "properties": {
"id": { "type": "string" },
"first_seen": { "type": "string", "format": "date-time" },
"last_seen": { "type": "string", "format": "date-time" },
@@ -30,13 +31,10 @@
"count": { "type": "integer" },
"external_url": { "type": "string" },
"external_base_url": { "type": "string" },
- "user_count": { "type": "integer"},
+ "user_count": { "type": "integer" },
"tags": {
"type": "object",
- "required" : [
- "level",
- "logger"
- ],
+ "required": ["level", "logger"],
"properties": {
"level": {
"type": "string"
@@ -57,7 +55,8 @@
"first_release_last_commit": { "type": ["string", "null"] },
"last_release_last_commit": { "type": ["string", "null"] },
"first_release_short_version": { "type": ["string", "null"] },
- "last_release_short_version": { "type": ["string", "null"] }
+ "last_release_short_version": { "type": ["string", "null"] },
+ "gitlab_commit": { "type": ["string", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/lib/gitlab/import_export/complex/project.json b/spec/fixtures/lib/gitlab/import_export/complex/project.json
index 583d6c7b78a..c33a88366d5 100644
--- a/spec/fixtures/lib/gitlab/import_export/complex/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/complex/project.json
@@ -80,6 +80,17 @@
"issue_id": 40
}
],
+ "award_emoji": [
+ {
+ "id": 1,
+ "name": "musical_keyboard",
+ "user_id": 1,
+ "awardable_type": "Issue",
+ "awardable_id": 40,
+ "created_at": "2020-01-07T11:55:22.234Z",
+ "updated_at": "2020-01-07T11:55:22.234Z"
+ }
+ ],
"zoom_meetings": [
{
"id": 1,
@@ -188,7 +199,18 @@
"author": {
"name": "User 4"
},
- "events": []
+ "events": [],
+ "award_emoji": [
+ {
+ "id": 1,
+ "name": "clapper",
+ "user_id": 1,
+ "awardable_type": "Note",
+ "awardable_id": 351,
+ "created_at": "2020-01-07T11:55:22.234Z",
+ "updated_at": "2020-01-07T11:55:22.234Z"
+ }
+ ]
},
{
"id": 352,
@@ -2297,7 +2319,32 @@
"updated_at": "2019-11-05T15:37:24.645Z"
}
],
- "notes": []
+ "notes": [
+ {
+ "id": 872,
+ "note": "This is a test note",
+ "noteable_type": "Snippet",
+ "author_id": 1,
+ "created_at": "2019-11-05T15:37:24.645Z",
+ "updated_at": "2019-11-05T15:37:24.645Z",
+ "noteable_id": 1,
+ "author": {
+ "name": "Random name"
+ },
+ "events": [],
+ "award_emoji": [
+ {
+ "id": 12,
+ "name": "thumbsup",
+ "user_id": 1,
+ "awardable_type": "Note",
+ "awardable_id": 872,
+ "created_at": "2019-11-05T15:37:21.287Z",
+ "updated_at": "2019-11-05T15:37:21.287Z"
+ }
+ ]
+ }
+ ]
}
],
"releases": [],
@@ -2434,7 +2481,18 @@
"author": {
"name": "User 4"
},
- "events": []
+ "events": [],
+ "award_emoji": [
+ {
+ "id": 1,
+ "name": "tada",
+ "user_id": 1,
+ "awardable_type": "Note",
+ "awardable_id": 1,
+ "created_at": "2019-11-05T15:37:21.287Z",
+ "updated_at": "2019-11-05T15:37:21.287Z"
+ }
+ ]
},
{
"id": 672,
@@ -2840,7 +2898,27 @@
"author_id": 1
}
],
- "approvals_before_merge": 1
+ "approvals_before_merge": 1,
+ "award_emoji": [
+ {
+ "id": 1,
+ "name": "thumbsup",
+ "user_id": 1,
+ "awardable_type": "MergeRequest",
+ "awardable_id": 27,
+ "created_at": "2020-01-07T11:21:21.235Z",
+ "updated_at": "2020-01-07T11:21:21.235Z"
+ },
+ {
+ "id": 2,
+ "name": "drum",
+ "user_id": 1,
+ "awardable_type": "MergeRequest",
+ "awardable_id": 27,
+ "created_at": "2020-01-07T11:21:21.235Z",
+ "updated_at": "2020-01-07T11:21:21.235Z"
+ }
+ ]
},
{
"id": 26,
diff --git a/spec/javascripts/issue_spec.js b/spec/frontend/issue_spec.js
index 966aee72abb..586bd7f8529 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/frontend/issue_spec.js
@@ -6,7 +6,13 @@ import axios from '~/lib/utils/axios_utils';
import Issue from '~/issue';
import '~/lib/utils/text_utility';
-describe('Issue', function() {
+describe('Issue', () => {
+ let testContext;
+
+ beforeEach(() => {
+ testContext = {};
+ });
+
let $boxClosed, $boxOpen, $btn;
preloadFixtures('issues/closed-issue.html');
@@ -80,10 +86,18 @@ describe('Issue', function() {
}
[true, false].forEach(isIssueInitiallyOpen => {
- describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() {
+ describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, () => {
const action = isIssueInitiallyOpen ? 'close' : 'reopen';
let mock;
+ function setup() {
+ testContext.issue = new Issue();
+ expectIssueState(isIssueInitiallyOpen);
+
+ testContext.$projectIssuesCounter = $('.issue_counter').first();
+ testContext.$projectIssuesCounter.text('1,001');
+ }
+
function mockCloseButtonResponseSuccess(url, response) {
mock.onPut(url).reply(() => {
expectNewBranchButtonState(true, false);
@@ -103,7 +117,7 @@ describe('Issue', function() {
});
}
- beforeEach(function() {
+ beforeEach(() => {
if (isIssueInitiallyOpen) {
loadFixtures('issues/open-issue.html');
} else {
@@ -111,19 +125,11 @@ describe('Issue', function() {
}
mock = new MockAdapter(axios);
-
mock.onGet(/(.*)\/related_branches$/).reply(200, {});
+ jest.spyOn(axios, 'get');
findElements(isIssueInitiallyOpen);
- this.issue = new Issue();
- expectIssueState(isIssueInitiallyOpen);
-
- this.$triggeredButton = $btn;
-
- this.$projectIssuesCounter = $('.issue_counter').first();
- this.$projectIssuesCounter.text('1,001');
-
- spyOn(axios, 'get').and.callThrough();
+ testContext.$triggeredButton = $btn;
});
afterEach(() => {
@@ -131,82 +137,90 @@ describe('Issue', function() {
$('div.flash-alert').remove();
});
- it(`${action}s the issue`, function(done) {
- mockCloseButtonResponseSuccess(this.$triggeredButton.attr('href'), {
+ it(`${action}s the issue`, done => {
+ mockCloseButtonResponseSuccess(testContext.$triggeredButton.attr('href'), {
id: 34,
});
mockCanCreateBranch(!isIssueInitiallyOpen);
- this.$triggeredButton.trigger('click');
+ setup();
+ testContext.$triggeredButton.trigger('click');
- setTimeout(() => {
+ setImmediate(() => {
expectIssueState(!isIssueInitiallyOpen);
- expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
+ expect(testContext.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
+ expect(testContext.$projectIssuesCounter.text()).toBe(
+ isIssueInitiallyOpen ? '1,000' : '1,002',
+ );
expectNewBranchButtonState(false, !isIssueInitiallyOpen);
done();
});
});
- it(`fails to ${action} the issue if saved:false`, function(done) {
- mockCloseButtonResponseSuccess(this.$triggeredButton.attr('href'), {
+ it(`fails to ${action} the issue if saved:false`, done => {
+ mockCloseButtonResponseSuccess(testContext.$triggeredButton.attr('href'), {
saved: false,
});
mockCanCreateBranch(isIssueInitiallyOpen);
- this.$triggeredButton.trigger('click');
+ setup();
+ testContext.$triggeredButton.trigger('click');
- setTimeout(() => {
+ setImmediate(() => {
expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
+ expect(testContext.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expectErrorMessage();
- expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expect(testContext.$projectIssuesCounter.text()).toBe('1,001');
expectNewBranchButtonState(false, isIssueInitiallyOpen);
done();
});
});
- it(`fails to ${action} the issue if HTTP error occurs`, function(done) {
- mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
+ it(`fails to ${action} the issue if HTTP error occurs`, done => {
+ mockCloseButtonResponseError(testContext.$triggeredButton.attr('href'));
mockCanCreateBranch(isIssueInitiallyOpen);
- this.$triggeredButton.trigger('click');
+ setup();
+ testContext.$triggeredButton.trigger('click');
- setTimeout(() => {
+ setImmediate(() => {
expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
+ expect(testContext.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expectErrorMessage();
- expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expect(testContext.$projectIssuesCounter.text()).toBe('1,001');
expectNewBranchButtonState(false, isIssueInitiallyOpen);
done();
});
});
- it('disables the new branch button if Ajax call fails', function() {
- mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
+ it('disables the new branch button if Ajax call fails', () => {
+ mockCloseButtonResponseError(testContext.$triggeredButton.attr('href'));
mock.onGet(/(.*)\/can_create_branch$/).networkError();
- this.$triggeredButton.trigger('click');
+ setup();
+ testContext.$triggeredButton.trigger('click');
expectNewBranchButtonState(false, false);
});
- it('does not trigger Ajax call if new branch button is missing', function(done) {
- mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
- Issue.$btnNewBranch = $();
- this.canCreateBranchDeferred = null;
+ it('does not trigger Ajax call if new branch button is missing', done => {
+ mockCloseButtonResponseError(testContext.$triggeredButton.attr('href'));
+
+ document.querySelector('#related-branches').remove();
+ document.querySelector('.create-mr-dropdown-wrap').remove();
- this.$triggeredButton.trigger('click');
+ setup();
+ testContext.$triggeredButton.trigger('click');
- setTimeout(() => {
+ setImmediate(() => {
expect(axios.get).not.toHaveBeenCalled();
done();
diff --git a/spec/frontend/lib/utils/poll_until_complete_spec.js b/spec/frontend/lib/utils/poll_until_complete_spec.js
new file mode 100644
index 00000000000..15602b87b9c
--- /dev/null
+++ b/spec/frontend/lib/utils/poll_until_complete_spec.js
@@ -0,0 +1,89 @@
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import pollUntilComplete from '~/lib/utils/poll_until_complete';
+import httpStatusCodes from '~/lib/utils/http_status';
+import { TEST_HOST } from 'helpers/test_constants';
+
+const endpoint = `${TEST_HOST}/foo`;
+const mockData = 'mockData';
+const pollInterval = 1234;
+const pollIntervalHeader = {
+ 'Poll-Interval': pollInterval,
+};
+
+describe('pollUntilComplete', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new AxiosMockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('given an immediate success response', () => {
+ beforeEach(() => {
+ mock.onGet(endpoint).replyOnce(httpStatusCodes.OK, mockData);
+ });
+
+ it('resolves with the response', () =>
+ pollUntilComplete(endpoint).then(({ data }) => {
+ expect(data).toBe(mockData);
+ }));
+ });
+
+ describe(`given the endpoint returns NO_CONTENT with a Poll-Interval before succeeding`, () => {
+ beforeEach(() => {
+ mock
+ .onGet(endpoint)
+ .replyOnce(httpStatusCodes.NO_CONTENT, undefined, pollIntervalHeader)
+ .onGet(endpoint)
+ .replyOnce(httpStatusCodes.OK, mockData);
+ });
+
+ it('calls the endpoint until it succeeds, and resolves with the response', () =>
+ Promise.all([
+ pollUntilComplete(endpoint).then(({ data }) => {
+ expect(data).toBe(mockData);
+ expect(mock.history.get).toHaveLength(2);
+ }),
+
+ // To ensure the above pollUntilComplete() promise is actually
+ // fulfilled, we must explictly run the timers forward by the time
+ // indicated in the headers *after* each previous request has been
+ // fulfilled.
+ axios
+ // wait for initial NO_CONTENT response to be fulfilled
+ .waitForAll()
+ .then(() => {
+ jest.advanceTimersByTime(pollInterval);
+ }),
+ ]));
+ });
+
+ describe('given the endpoint returns an error status', () => {
+ const errorMessage = 'error message';
+
+ beforeEach(() => {
+ mock.onGet(endpoint).replyOnce(httpStatusCodes.NOT_FOUND, errorMessage);
+ });
+
+ it('rejects with the error response', () =>
+ pollUntilComplete(endpoint).catch(error => {
+ expect(error.response.data).toBe(errorMessage);
+ }));
+ });
+
+ describe('given params', () => {
+ const params = { foo: 'bar' };
+ beforeEach(() => {
+ mock.onGet(endpoint, { params }).replyOnce(httpStatusCodes.OK, mockData);
+ });
+
+ it('requests the expected URL', () =>
+ pollUntilComplete(endpoint, { params }).then(({ data }) => {
+ expect(data).toBe(mockData);
+ }));
+ });
+});
diff --git a/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb b/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb
index 3576adb5272..15d53644884 100644
--- a/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb
@@ -30,6 +30,7 @@ describe GitlabSchema.types['SentryDetailedError'] do
lastReleaseLastCommit
firstReleaseShortVersion
lastReleaseShortVersion
+ gitlabCommit
]
is_expected.to have_graphql_fields(*expected_fields)
diff --git a/spec/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications_spec.rb b/spec/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications_spec.rb
new file mode 100644
index 00000000000..9d9281f4e98
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::ActivatePrometheusServicesForSharedClusterApplications, :migration, schema: 2019_12_20_102807 do
+ include MigrationHelpers::PrometheusServiceHelpers
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:services) { table(:services) }
+ let(:namespace) { namespaces.create(name: 'user', path: 'user') }
+ let(:project) { projects.create(namespace_id: namespace.id) }
+
+ let(:columns) do
+ %w(project_id active properties type template push_events
+ issues_events merge_requests_events tag_push_events
+ note_events category default wiki_page_events pipeline_events
+ confidential_issues_events commit_events job_events
+ confidential_note_events deployment_events)
+ end
+
+ describe '#perform' do
+ it 'is idempotent' do
+ expect { subject.perform(project.id) }.to change { services.order(:id).map { |row| row.attributes } }
+
+ expect { subject.perform(project.id) }.not_to change { services.order(:id).map { |row| row.attributes } }
+ end
+
+ context 'non prometheus services' do
+ it 'does not change them' do
+ other_type = 'SomeOtherService'
+ services.create(service_params_for(project.id, active: true, type: other_type))
+
+ expect { subject.perform(project.id) }.not_to change { services.where(type: other_type).order(:id).map { |row| row.attributes } }
+ end
+ end
+
+ context 'prometheus services are configured manually ' do
+ it 'does not change them' do
+ properties = '{"api_url":"http://test.dev","manual_configuration":"1"}'
+ services.create(service_params_for(project.id, properties: properties, active: false))
+
+ expect { subject.perform(project.id) }.not_to change { services.order(:id).map { |row| row.attributes } }
+ end
+ end
+
+ context 'prometheus integration services do not exist' do
+ it 'creates missing services entries' do
+ subject.perform(project.id)
+
+ rows = services.order(:id).map { |row| row.attributes.slice(*columns).symbolize_keys }
+
+ expect([service_params_for(project.id, active: true)]).to eq rows
+ end
+ end
+
+ context 'prometheus integration services exist' do
+ context 'in active state' do
+ it 'does not change them' do
+ services.create(service_params_for(project.id, active: true))
+
+ expect { subject.perform(project.id) }.not_to change { services.order(:id).map { |row| row.attributes } }
+ end
+ end
+
+ context 'not in active state' do
+ it 'sets active attribute to true' do
+ service = services.create(service_params_for(project.id))
+
+ expect { subject.perform(project.id) }.to change { service.reload.active? }.from(false).to(true)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index dae5b028c15..4d12d05211b 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -219,6 +219,16 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(award_emoji.map(&:name)).to contain_exactly('thumbsup', 'coffee')
end
+ it 'snippet has notes' do
+ expect(@project.snippets.first.notes.count).to eq(1)
+ end
+
+ it 'snippet has award emojis on notes' do
+ award_emoji = @project.snippets.first.notes.first.award_emoji.first
+
+ expect(award_emoji.name).to eq('thumbsup')
+ end
+
it 'restores `ci_cd_settings` : `group_runners_enabled` setting' do
expect(@project.ci_cd_settings.group_runners_enabled?).to eq(false)
end
@@ -240,6 +250,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(sentry_issue.sentry_issue_identifier).to eq(1234567891)
end
+ it 'has award emoji for an issue' do
+ award_emoji = @project.issues.first.award_emoji.first
+
+ expect(award_emoji.name).to eq('musical_keyboard')
+ end
+
+ it 'has award emoji for a note in an issue' do
+ award_emoji = @project.issues.first.notes.first.award_emoji.first
+
+ expect(award_emoji.name).to eq('clapper')
+ end
+
it 'restores container_expiration_policy' do
policy = Project.find_by_path('project').container_expiration_policy
@@ -266,6 +288,20 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it 'has no source if source/target differ' do
expect(MergeRequest.find_by_title('MR2').source_project_id).to be_nil
end
+
+ it 'has award emoji' do
+ award_emoji = MergeRequest.find_by_title('MR1').award_emoji
+
+ expect(award_emoji.map(&:name)).to contain_exactly('thumbsup', 'drum')
+ end
+
+ context 'notes' do
+ it 'has award emoji' do
+ award_emoji = MergeRequest.find_by_title('MR1').notes.first.award_emoji.first
+
+ expect(award_emoji.name).to eq('tada')
+ end
+ end
end
context 'tokens are regenerated' do
diff --git a/spec/lib/sentry/client/issue_spec.rb b/spec/lib/sentry/client/issue_spec.rb
index f4796a27b4d..e10d27e4342 100644
--- a/spec/lib/sentry/client/issue_spec.rb
+++ b/spec/lib/sentry/client/issue_spec.rb
@@ -263,6 +263,7 @@ describe Sentry::Client::Issue do
:last_release_last_commit | [:lastRelease, :lastCommit]
:first_release_short_version | [:firstRelease, :shortVersion]
:last_release_short_version | [:lastRelease, :shortVersion]
+ :first_release_version | [:firstRelease, :version]
end
with_them do
diff --git a/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb b/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb
new file mode 100644
index 00000000000..b0d2aea7015
--- /dev/null
+++ b/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20200107172020_add_timestamp_softwarelicensespolicy.rb')
+
+describe AddTimestampSoftwarelicensespolicy, :migration do
+ let(:software_licenses_policy) { table(:software_license_policies) }
+ let(:projects) { table(:projects) }
+ let(:licenses) { table(:software_licenses) }
+
+ before do
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+ licenses.create!(name: 'MIT')
+ software_licenses_policy.create!(project_id: projects.first.id, software_license_id: licenses.first.id)
+ end
+
+ it 'creates timestamps' do
+ migrate!
+
+ expect(software_licenses_policy.first.created_at).to be_nil
+ expect(software_licenses_policy.first.updated_at).to be_nil
+ end
+end
diff --git a/spec/migrations/patch_prometheus_services_for_shared_cluster_applications_spec.rb b/spec/migrations/patch_prometheus_services_for_shared_cluster_applications_spec.rb
new file mode 100644
index 00000000000..74f6d56a2eb
--- /dev/null
+++ b/spec/migrations/patch_prometheus_services_for_shared_cluster_applications_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20191220102807_patch_prometheus_services_for_shared_cluster_applications.rb')
+
+describe PatchPrometheusServicesForSharedClusterApplications, :migration, :sidekiq do
+ include MigrationHelpers::PrometheusServiceHelpers
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:services) { table(:services) }
+ let(:clusters) { table(:clusters) }
+ let(:cluster_groups) { table(:cluster_groups) }
+ let(:clusters_applications_prometheus) { table(:clusters_applications_prometheus) }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+
+ let(:application_statuses) do
+ {
+ errored: -1,
+ installed: 3,
+ updated: 5
+ }
+ end
+
+ let(:cluster_types) do
+ {
+ instance_type: 1,
+ group_type: 2
+ }
+ end
+
+ describe '#up' do
+ let!(:project_with_missing_service) { projects.create!(name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id) }
+ let(:project_with_inactive_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_active_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_manual_active_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_manual_inactive_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_active_not_prometheus_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_inactive_not_prometheus_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+
+ before do
+ services.create(service_params_for(project_with_inactive_service.id, active: false))
+ services.create(service_params_for(project_with_active_service.id, active: true))
+ services.create(service_params_for(project_with_active_not_prometheus_service.id, active: true, type: 'other'))
+ services.create(service_params_for(project_with_inactive_not_prometheus_service.id, active: false, type: 'other'))
+ services.create(service_params_for(project_with_manual_inactive_service.id, active: false, properties: { some: 'data' }.to_json))
+ services.create(service_params_for(project_with_manual_active_service.id, active: true, properties: { some: 'data' }.to_json))
+ end
+
+ shared_examples 'patch prometheus services post migration' do
+ context 'prometheus application is installed on the cluster' do
+ it 'schedules a background migration' do
+ clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:installed], version: '123')
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ background_migrations = [["ActivatePrometheusServicesForSharedClusterApplications", project_with_missing_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_inactive_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_active_not_prometheus_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_inactive_not_prometheus_service.id]]
+
+ migrate!
+
+ enqueued_migrations = BackgroundMigrationWorker.jobs.map { |job| job['args'] }
+ expect(enqueued_migrations).to match_array(background_migrations)
+ end
+ end
+ end
+ end
+
+ context 'prometheus application was recently updated on the cluster' do
+ it 'schedules a background migration' do
+ clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:updated], version: '123')
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ background_migrations = [["ActivatePrometheusServicesForSharedClusterApplications", project_with_missing_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_inactive_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_active_not_prometheus_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_inactive_not_prometheus_service.id]]
+
+ migrate!
+
+ enqueued_migrations = BackgroundMigrationWorker.jobs.map { |job| job['args'] }
+ expect(enqueued_migrations).to match_array(background_migrations)
+ end
+ end
+ end
+ end
+
+ context 'prometheus application failed to install on the cluster' do
+ it 'does not schedule a background migration' do
+ clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:errored], version: '123')
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq 0
+ end
+ end
+ end
+ end
+
+ context 'prometheus application is NOT installed on the cluster' do
+ it 'does not schedule a background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq 0
+ end
+ end
+ end
+ end
+ end
+
+ context 'Cluster is group_type' do
+ let(:cluster) { clusters.create(name: 'cluster', cluster_type: cluster_types[:group_type]) }
+
+ before do
+ cluster_groups.create(group_id: namespace.id, cluster_id: cluster.id)
+ end
+
+ it_behaves_like 'patch prometheus services post migration'
+ end
+
+ context 'Cluster is instance_type' do
+ let(:cluster) { clusters.create(name: 'cluster', cluster_type: cluster_types[:instance_type]) }
+
+ it_behaves_like 'patch prometheus services post migration'
+ end
+ end
+end
diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
index 03e4c04f97e..3dfdd9c1392 100644
--- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb
+++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
@@ -210,6 +210,53 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
end
end
+ describe '#issue_details' do
+ let(:issue) { build(:detailed_error_tracking_error) }
+ let(:sentry_client) { double('sentry_client', issue_details: issue) }
+ let(:commit_id) { '123456' }
+
+ let(:result) do
+ subject.issue_details
+ end
+
+ context 'when cached' do
+ before do
+ stub_reactive_cache(subject, issue, {})
+ synchronous_reactive_cache(subject)
+
+ expect(subject).to receive(:sentry_client).and_return(sentry_client)
+ end
+
+ it { expect(result).to eq(issue: issue) }
+ it { expect(result[:issue].first_release_version).to eq(commit_id) }
+ it { expect(result[:issue].gitlab_commit).to eq(nil) }
+
+ context 'when release version is nil' do
+ before do
+ issue.first_release_version = nil
+ end
+
+ it { expect(result[:issue].gitlab_commit).to eq(nil) }
+ end
+
+ context 'when repo commit matches first relase version' do
+ let(:commit) { double('commit', id: commit_id) }
+ let(:repository) { double('repository', commit: commit) }
+
+ before do
+ expect(project).to receive(:repository).and_return(repository)
+ end
+
+ it { expect(result[:issue].gitlab_commit).to eq(commit_id) }
+ end
+ end
+
+ context 'when not cached' do
+ it { expect(subject).not_to receive(:sentry_client) }
+ it { expect(result).to be_nil }
+ end
+ end
+
describe '#update_issue' do
let(:opts) do
{ status: 'resolved' }
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index cadb8793e15..2f84b92b806 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -181,4 +181,10 @@ RSpec.describe Release do
it { is_expected.to eq(release.evidence.summary) }
end
end
+
+ describe '#milestone_titles' do
+ let(:release) { create(:release, :with_milestones) }
+
+ it { expect(release.milestone_titles).to eq(release.milestones.map {|m| m.title }.sort.join(", "))}
+ end
end
diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
index d10380dab3a..664206dec29 100644
--- a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
+++ b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
@@ -56,6 +56,7 @@ describe 'getting a detailed sentry error' do
expect(error_data['status']).to eql sentry_detailed_error.status.upcase
expect(error_data['firstSeen']).to eql sentry_detailed_error.first_seen
expect(error_data['lastSeen']).to eql sentry_detailed_error.last_seen
+ expect(error_data['gitlabCommit']).to be nil
end
it 'is expected to return the frequency correctly' do
diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb
index 178bac3574f..f6c70873540 100644
--- a/spec/services/releases/update_service_spec.rb
+++ b/spec/services/releases/update_service_spec.rb
@@ -21,6 +21,7 @@ describe Releases::UpdateService do
it 'raises an error' do
result = service.execute
expect(result[:status]).to eq(:error)
+ expect(result[:milestones_updated]).to be_falsy
end
end
@@ -50,21 +51,33 @@ describe Releases::UpdateService do
end
context 'when a milestone is passed in' do
- let(:new_title) { 'v2.0' }
let(:milestone) { create(:milestone, project: project, title: 'v1.0') }
- let(:new_milestone) { create(:milestone, project: project, title: new_title) }
let(:params_with_milestone) { params.merge!({ milestones: [new_title] }) }
+ let(:new_milestone) { create(:milestone, project: project, title: new_title) }
let(:service) { described_class.new(new_milestone.project, user, params_with_milestone) }
before do
release.milestones << milestone
+ end
- service.execute
- release.reload
+ context 'a different milestone' do
+ let(:new_title) { 'v2.0' }
+
+ it 'updates the related milestone accordingly' do
+ result = service.execute
+ release.reload
+
+ expect(release.milestones.first.title).to eq(new_title)
+ expect(result[:milestones_updated]).to be_truthy
+ end
end
- it 'updates the related milestone accordingly' do
- expect(release.milestones.first.title).to eq(new_title)
+ context 'an identical milestone' do
+ let(:new_title) { 'v1.0' }
+
+ it "raises an error" do
+ expect { service.execute }.to raise_error(ActiveRecord::RecordInvalid)
+ end
end
end
@@ -76,12 +89,14 @@ describe Releases::UpdateService do
release.milestones << milestone
service.params = params_with_empty_milestone
- service.execute
- release.reload
end
it 'removes the old milestone and does not associate any new milestone' do
+ result = service.execute
+ release.reload
+
expect(release.milestones).not_to be_present
+ expect(result[:milestones_updated]).to be_truthy
end
end
@@ -96,14 +111,15 @@ describe Releases::UpdateService do
create(:milestone, project: project, title: new_title_1)
create(:milestone, project: project, title: new_title_2)
release.milestones << milestone
-
- service.execute
- release.reload
end
it 'removes the old milestone and update the release with the new ones' do
+ result = service.execute
+ release.reload
+
milestone_titles = release.milestones.map(&:title)
expect(milestone_titles).to match_array([new_title_1, new_title_2])
+ expect(result[:milestones_updated]).to be_truthy
end
end
end
diff --git a/spec/support/migrations_helpers/prometheus_service_helpers.rb b/spec/support/migrations_helpers/prometheus_service_helpers.rb
new file mode 100644
index 00000000000..88f2f71ee1e
--- /dev/null
+++ b/spec/support/migrations_helpers/prometheus_service_helpers.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module MigrationHelpers
+ module PrometheusServiceHelpers
+ def service_params_for(project_id, params = {})
+ {
+ project_id: project_id,
+ active: false,
+ properties: '{}',
+ type: 'PrometheusService',
+ template: false,
+ push_events: true,
+ issues_events: true,
+ merge_requests_events: true,
+ tag_push_events: true,
+ note_events: true,
+ category: 'monitoring',
+ default: false,
+ wiki_page_events: true,
+ pipeline_events: true,
+ confidential_issues_events: true,
+ commit_events: true,
+ job_events: true,
+ confidential_note_events: true,
+ deployment_events: false
+ }.merge(params)
+ end
+
+ def row_attributes(entity)
+ entity.attributes.with_indifferent_access.tap do |hash|
+ hash.merge!(hash.slice(:created_at, :updated_at).transform_values { |v| v.to_s(:db) })
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb b/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb
new file mode 100644
index 00000000000..89c0841fbd6
--- /dev/null
+++ b/spec/support/shared_examples/workers/self_monitoring_shared_examples.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+# This shared_example requires the following variables:
+# let(:service_class) { Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService }
+# let(:service) { instance_double(service_class) }
+RSpec.shared_examples 'executes service' do
+ before do
+ allow(service_class).to receive(:new) { service }
+ end
+
+ it 'runs the service' do
+ expect(service).to receive(:execute)
+
+ subject.perform
+ end
+end
+
+RSpec.shared_examples 'returns in_progress based on Sidekiq::Status' do
+ it 'returns true when job is enqueued' do
+ jid = described_class.perform_async
+
+ expect(described_class.in_progress?(jid)).to eq(true)
+ end
+
+ it 'returns false when job does not exist' do
+ expect(described_class.in_progress?('fake_jid')).to eq(false)
+ end
+end
diff --git a/spec/workers/self_monitoring_project_create_worker_spec.rb b/spec/workers/self_monitoring_project_create_worker_spec.rb
index 75c4f5d49d1..00c288bdc46 100644
--- a/spec/workers/self_monitoring_project_create_worker_spec.rb
+++ b/spec/workers/self_monitoring_project_create_worker_spec.rb
@@ -7,22 +7,10 @@ describe SelfMonitoringProjectCreateWorker do
let(:service_class) { Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService }
let(:service) { instance_double(service_class) }
- before do
- allow(service_class).to receive(:new) { service }
- end
-
- it 'runs the SelfMonitoring::Project::CreateService' do
- expect(service).to receive(:execute)
-
- subject.perform
- end
+ it_behaves_like 'executes service'
end
describe '.in_progress?', :clean_gitlab_redis_shared_state do
- it 'returns in_progress when job is enqueued' do
- jid = described_class.perform_async
-
- expect(described_class.in_progress?(jid)).to eq(true)
- end
+ it_behaves_like 'returns in_progress based on Sidekiq::Status'
end
end
diff --git a/spec/workers/self_monitoring_project_delete_worker_spec.rb b/spec/workers/self_monitoring_project_delete_worker_spec.rb
new file mode 100644
index 00000000000..3685c73513e
--- /dev/null
+++ b/spec/workers/self_monitoring_project_delete_worker_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SelfMonitoringProjectDeleteWorker do
+ let_it_be(:jid) { 'b5b28910d97563e58c2fe55f' }
+ let_it_be(:data_key) { "self_monitoring_delete_result:#{jid}" }
+
+ describe '#perform' do
+ let(:service_class) { Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService }
+ let(:service) { instance_double(service_class) }
+
+ it_behaves_like 'executes service'
+ end
+
+ describe '.status', :clean_gitlab_redis_shared_state do
+ it_behaves_like 'returns in_progress based on Sidekiq::Status'
+ end
+end