diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-10 18:07:43 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-10 18:07:43 +0000 |
commit | 6f0f893bd87535b61e0ecb1ce069eaa7fcb9e5be (patch) | |
tree | 8af92b29c838e9af2fd70f9a4a2314a08f4af922 /spec | |
parent | 8b1228b0d409d7751f01d9fb72ebfbbf62399486 (diff) | |
download | gitlab-ce-6f0f893bd87535b61e0ecb1ce069eaa7fcb9e5be.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
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 |