diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-29 15:09:08 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-29 15:09:08 +0300 |
commit | 7cc6872401eb487ed20dbb9d455f8bb9c97d9e39 (patch) | |
tree | 63f6ed5d4e6c5cec31c43363626d9f5b178eddf8 /spec | |
parent | 46b10c0fc884400941c17e2777b242ac54d111e5 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
22 files changed, 663 insertions, 137 deletions
diff --git a/spec/factories/error_tracking/detailed_error.rb b/spec/factories/error_tracking/detailed_error.rb index 07b6c53e3cd..83004ffae38 100644 --- a/spec/factories/error_tracking/detailed_error.rb +++ b/spec/factories/error_tracking/detailed_error.rb @@ -1,41 +1,20 @@ # frozen_string_literal: true FactoryBot.define do - factory :detailed_error_tracking_error, class: 'Gitlab::ErrorTracking::DetailedError' do - id { '1' } - title { 'title' } - type { 'error' } - user_count { 1 } - count { 2 } - first_seen { Time.now.iso8601 } - last_seen { Time.now.iso8601 } - message { 'message' } - culprit { 'culprit' } - external_url { 'http://example.com/id' } + factory :detailed_error_tracking_error, parent: :error_tracking_error, class: 'Gitlab::ErrorTracking::DetailedError' do + gitlab_issue { 'http://gitlab.example.com/issues/1' } external_base_url { 'http://example.com' } - project_id { 'project1' } - project_name { 'project name' } - project_slug { 'project_name' } - short_id { 'ID' } - status { 'unresolved' } + first_release_last_commit { '68c914da9' } + last_release_last_commit { '9ad419c86' } + first_release_short_version { 'abc123' } + last_release_short_version { 'abc123' } + first_release_version { '12345678' } tags do { level: 'error', logger: 'rails' } end - frequency do - [ - [Time.now.to_i, 10] - ] - end - gitlab_issue { 'http://gitlab.example.com/issues/1' } - first_release_last_commit { '68c914da9' } - last_release_last_commit { '9ad419c86' } - first_release_short_version { 'abc123' } - last_release_short_version { 'abc123' } - first_release_version { '12345678' } - skip_create end end diff --git a/spec/factories/error_tracking/error.rb b/spec/factories/error_tracking/error.rb index 5be1f074555..e5f2e2ca9a7 100644 --- a/spec/factories/error_tracking/error.rb +++ b/spec/factories/error_tracking/error.rb @@ -2,13 +2,13 @@ FactoryBot.define do factory :error_tracking_error, class: 'Gitlab::ErrorTracking::Error' do - id { 'id' } + id { '1' } title { 'title' } type { 'error' } user_count { 1 } count { 2 } - first_seen { Time.now } - last_seen { Time.now } + first_seen { Time.now.iso8601 } + last_seen { Time.now.iso8601 } message { 'message' } culprit { 'culprit' } external_url { 'http://example.com/id' } @@ -17,7 +17,11 @@ FactoryBot.define do project_slug { 'project_name' } short_id { 'ID' } status { 'unresolved' } - frequency { [] } + frequency do + [ + [Time.now.to_i, 10] + ] + end skip_create end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 7126707affd..831bcf8931e 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -32,7 +32,7 @@ describe 'issue move to another project' do let(:new_project) { create(:project) } let(:new_project_search) { create(:project) } let(:text) { "Text with #{mr.to_reference}" } - let(:cross_reference) { old_project.to_reference(new_project) } + let(:cross_reference) { old_project.to_reference_base(new_project) } before do old_project.add_reporter(user) diff --git a/spec/fixtures/lib/gitlab/import_export/with_duplicates.json b/spec/fixtures/lib/gitlab/import_export/with_duplicates.json new file mode 100644 index 00000000000..ed2e1821dd3 --- /dev/null +++ b/spec/fixtures/lib/gitlab/import_export/with_duplicates.json @@ -0,0 +1,43 @@ +{ + "simple": 42, + "duped_hash_with_id": { + "id": 0, + "v1": 1 + }, + "duped_hash_no_id": { + "v1": 1 + }, + "duped_array": [ + "v2" + ], + "array": [ + { + "duped_hash_with_id": { + "id": 0, + "v1": 1 + } + }, + { + "duped_array": [ + "v2" + ] + }, + { + "duped_hash_no_id": { + "v1": 1 + } + } + ], + "nested": { + "duped_hash_with_id": { + "id": 0, + "v1": 1 + }, + "duped_array": [ + "v2" + ], + "array": [ + "don't touch" + ] + } +}
\ No newline at end of file diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js index 996804f6d08..5b81d034e14 100644 --- a/spec/frontend/registry/settings/components/settings_form_spec.js +++ b/spec/frontend/registry/settings/components/settings_form_spec.js @@ -1,4 +1,5 @@ import { mount } from '@vue/test-utils'; +import Tracking from '~/tracking'; import stubChildren from 'helpers/stub_children'; import component from '~/registry/settings/components/settings_form.vue'; import { createStore } from '~/registry/settings/store/'; @@ -15,6 +16,9 @@ describe('Settings Form', () => { let dispatchSpy; const FORM_ELEMENTS_ID_PREFIX = '#expiration-policy'; + const trackingPayload = { + label: 'docker_container_retention_and_expiration_policies', + }; const GlLoadingIcon = { name: 'gl-loading-icon-stub', template: '<svg></svg>' }; @@ -48,6 +52,7 @@ describe('Settings Form', () => { store.dispatch('setInitialState', stringifiedFormOptions); dispatchSpy = jest.spyOn(store, 'dispatch'); mountComponent(); + jest.spyOn(Tracking, 'event'); }); afterEach(() => { @@ -118,15 +123,23 @@ describe('Settings Form', () => { beforeEach(() => { form = findForm(); }); - it('cancel has type reset', () => { - expect(findCancelButton().attributes('type')).toBe('reset'); - }); - it('form reset event call the appropriate function', () => { - dispatchSpy.mockReturnValue(); - form.trigger('reset'); - // expect.any(Object) is necessary because the event payload is passed to the function - expect(dispatchSpy).toHaveBeenCalledWith('resetSettings', expect.any(Object)); + describe('form cancel event', () => { + it('has type reset', () => { + expect(findCancelButton().attributes('type')).toBe('reset'); + }); + + it('calls the appropriate function', () => { + dispatchSpy.mockReturnValue(); + form.trigger('reset'); + expect(dispatchSpy).toHaveBeenCalledWith('resetSettings'); + }); + + it('tracks the reset event', () => { + dispatchSpy.mockReturnValue(); + form.trigger('reset'); + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'reset_form', trackingPayload); + }); }); it('save has type submit', () => { @@ -177,6 +190,12 @@ describe('Settings Form', () => { expect(dispatchSpy).toHaveBeenCalledWith('saveSettings'); }); + it('tracks the submit event', () => { + dispatchSpy.mockResolvedValue(); + form.trigger('submit'); + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload); + }); + it('show a success toast when submit succeed', () => { dispatchSpy.mockResolvedValue(); form.trigger('submit'); diff --git a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb new file mode 100644 index 00000000000..3bb8a5c389d --- /dev/null +++ b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::ErrorTracking::SentryErrorCollectionResolver do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:current_user) { create(:user) } + + let(:list_issues_service) { spy('ErrorTracking::ListIssuesService') } + + before do + project.add_developer(current_user) + + allow(ErrorTracking::ListIssuesService) + .to receive(:new) + .and_return list_issues_service + end + + describe '#resolve' do + it 'returns an error collection object' do + expect(resolve_error_collection).to be_a Gitlab::ErrorTracking::ErrorCollection + end + + it 'provides the service url' do + fake_url = 'http://test.com' + + expect(list_issues_service) + .to receive(:external_url) + .and_return(fake_url) + + result = resolve_error_collection + expect(result.external_url).to eq fake_url + end + + it 'provides the project' do + expect(resolve_error_collection.project).to eq project + end + end + + private + + def resolve_error_collection(context = { current_user: current_user }) + resolve(described_class, obj: project, args: {}, ctx: context) + end +end diff --git a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb new file mode 100644 index 00000000000..93f89d077d7 --- /dev/null +++ b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::ErrorTracking::SentryErrorsResolver do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:current_user) { create(:user) } + let_it_be(:error_collection) { Gitlab::ErrorTracking::ErrorCollection.new(project: project) } + + let(:list_issues_service) { spy('ErrorTracking::ListIssuesService') } + + let(:issues) { nil } + let(:pagination) { nil } + + describe '#resolve' do + context 'insufficient user permission' do + let(:user) { create(:user) } + + it 'returns nil' do + context = { current_user: user } + + expect(resolve_errors({}, context)).to eq nil + end + end + + context 'user with permission' do + before do + project.add_developer(current_user) + + allow(ErrorTracking::ListIssuesService) + .to receive(:new) + .and_return list_issues_service + end + + context 'when after arg given' do + let(:after) { "1576029072000:0:0" } + + it 'gives the cursor arg' do + expect(ErrorTracking::ListIssuesService) + .to receive(:new) + .with(project, current_user, { cursor: after }) + .and_return list_issues_service + + resolve_errors({ after: after }) + end + end + + context 'when no issues fetched' do + before do + allow(list_issues_service) + .to receive(:execute) + .and_return( + issues: nil + ) + end + it 'returns nil' do + expect(resolve_errors).to eq nil + end + end + + context 'when issues returned' do + let(:issues) { [:issue_1, :issue_2] } + let(:pagination) do + { + 'next' => { 'cursor' => 'next' }, + 'previous' => { 'cursor' => 'prev' } + } + end + + before do + allow(list_issues_service) + .to receive(:execute) + .and_return( + issues: issues, + pagination: pagination + ) + end + + it 'sets the issues' do + expect(resolve_errors).to contain_exactly(*issues) + end + + it 'sets the pagination variables' do + result = resolve_errors + expect(result.next_cursor).to eq 'next' + expect(result.previous_cursor).to eq 'prev' + end + + it 'returns an externally paginated array' do + expect(resolve_errors).to be_a Gitlab::Graphql::ExternallyPaginatedArray + end + end + end + end + + private + + def resolve_errors(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: error_collection, args: args, ctx: context) + end +end diff --git a/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb new file mode 100644 index 00000000000..1e6b7f89c08 --- /dev/null +++ b/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['SentryErrorCollection'] do + it { expect(described_class.graphql_name).to eq('SentryErrorCollection') } + + it { expect(described_class).to require_graphql_authorizations(:read_sentry_issue) } + + it 'exposes the expected fields' do + expected_fields = %i[ + errors + detailed_error + external_url + ] + + is_expected.to have_graphql_fields(*expected_fields) + end + + describe 'errors field' do + subject { described_class.fields['errors'] } + + it 'returns errors' do + aggregate_failures 'testing the correct types are returned' do + is_expected.to have_graphql_type(Types::ErrorTracking::SentryErrorType.connection_type) + is_expected.to have_graphql_extension(Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension) + is_expected.to have_graphql_resolver(Resolvers::ErrorTracking::SentryErrorsResolver) + end + end + end +end diff --git a/spec/graphql/types/error_tracking/sentry_error_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_type_spec.rb new file mode 100644 index 00000000000..51acd035024 --- /dev/null +++ b/spec/graphql/types/error_tracking/sentry_error_type_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['SentryError'] do + it { expect(described_class.graphql_name).to eq('SentryError') } + + it 'exposes the expected fields' do + expected_fields = %i[ + id + sentryId + title + type + userCount + count + firstSeen + lastSeen + message + culprit + externalUrl + sentryProjectId + sentryProjectName + sentryProjectSlug + shortId + status + frequency + ] + + is_expected.to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index a82b890be42..5cfb0e6e6f7 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -229,10 +229,10 @@ describe Banzai::Filter::CommitRangeReferenceFilter do end it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" + exp = act = "Fixed #{project2.to_reference_base}@#{commit1.id.reverse}...#{commit2.id}" expect(reference_filter(act).to_html).to eq exp - exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" + exp = act = "Fixed #{project2.to_reference_base}@#{commit1.id}...#{commit2.id.reverse}" expect(reference_filter(act).to_html).to eq exp end end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 66af26bc51c..82df5064896 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -369,7 +369,7 @@ describe Banzai::Filter::LabelReferenceFilter do end context 'with project reference' do - let(:reference) { "#{project.to_reference}#{group_label.to_reference(format: :name)}" } + let(:reference) { "#{project.to_reference_base}#{group_label.to_reference(format: :name)}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}", project: project) @@ -385,7 +385,7 @@ describe Banzai::Filter::LabelReferenceFilter do end it 'ignores invalid label names' do - exp = act = %(Label #{project.to_reference}#{Label.reference_prefix}"#{group_label.name.reverse}") + exp = act = %(Label #{project.to_reference_base}#{Label.reference_prefix}"#{group_label.name.reverse}") expect(reference_filter(act).to_html).to eq exp end diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 2fe8c9074df..0c8413adcba 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -367,15 +367,17 @@ describe Banzai::Filter::MilestoneReferenceFilter do expect(doc.css('a').first.text).to eq(urls.milestone_url(milestone)) end - it 'does not support cross-project references' do + it 'does not support cross-project references', :aggregate_failures do another_group = create(:group) another_project = create(:project, :public, group: group) - project_reference = another_project.to_reference(project) + project_reference = another_project.to_reference_base(project) + input_text = "See #{project_reference}#{reference}" milestone.update!(group: another_group) - doc = reference_filter("See #{project_reference}#{reference}") + doc = reference_filter(input_text) + expect(input_text).to match(Milestone.reference_pattern) expect(doc.css('a')).to be_empty end diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index d0b4542d503..a054b79ec03 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -10,7 +10,7 @@ describe Banzai::Filter::ProjectReferenceFilter do end def get_reference(project) - project.to_reference_with_postfix + project.to_reference end let(:project) { create(:project, :public) } diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index b0d2e049777..a3904f4a97c 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::Gfm::ReferenceRewriter do let(:new_project) { create(:project, name: 'new-project', group: group) } let(:user) { create(:user) } - let(:old_project_ref) { old_project.to_reference(new_project) } + let(:old_project_ref) { old_project.to_reference_base(new_project) } let(:text) { 'some text' } before do diff --git a/spec/lib/gitlab/import_export/project_tree_loader_spec.rb b/spec/lib/gitlab/import_export/project_tree_loader_spec.rb new file mode 100644 index 00000000000..b22de5a3f7b --- /dev/null +++ b/spec/lib/gitlab/import_export/project_tree_loader_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::ProjectTreeLoader do + let(:fixture) { 'spec/fixtures/lib/gitlab/import_export/with_duplicates.json' } + let(:project_tree) { JSON.parse(File.read(fixture)) } + + context 'without de-duplicating entries' do + let(:parsed_tree) do + subject.load(fixture) + end + + it 'parses the JSON into the expected tree' do + expect(parsed_tree).to eq(project_tree) + end + + it 'does not de-duplicate entries' do + expect(parsed_tree['duped_hash_with_id']).not_to be(parsed_tree['array'][0]['duped_hash_with_id']) + end + end + + context 'with de-duplicating entries' do + let(:parsed_tree) do + subject.load(fixture, dedup_entries: true) + end + + it 'parses the JSON into the expected tree' do + expect(parsed_tree).to eq(project_tree) + end + + it 'de-duplicates equal values' do + expect(parsed_tree['duped_hash_with_id']).to be(parsed_tree['array'][0]['duped_hash_with_id']) + expect(parsed_tree['duped_hash_with_id']).to be(parsed_tree['nested']['duped_hash_with_id']) + expect(parsed_tree['duped_array']).to be(parsed_tree['array'][1]['duped_array']) + expect(parsed_tree['duped_array']).to be(parsed_tree['nested']['duped_array']) + end + + it 'does not de-duplicate hashes without IDs' do + expect(parsed_tree['duped_hash_no_id']).to eq(parsed_tree['array'][2]['duped_hash_no_id']) + expect(parsed_tree['duped_hash_no_id']).not_to be(parsed_tree['array'][2]['duped_hash_no_id']) + end + + it 'keeps single entries intact' do + expect(parsed_tree['simple']).to eq(42) + expect(parsed_tree['nested']['array']).to eq(["don't touch"]) + 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 25f70420cda..129f119e148 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -450,7 +450,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'project.json file access check' do let(:user) { create(:user) } let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } - let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } + let(:project_tree_restorer) do + described_class.new(user: user, shared: shared, project: project) + end let(:restored_project_json) { project_tree_restorer.restore } it 'does not read a symlink' do @@ -725,7 +727,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do let(:project) { create(:project) } let(:user) { create(:user) } let(:tree_hash) { { 'visibility_level' => visibility } } - let(:restorer) { described_class.new(user: user, shared: shared, project: project) } + let(:restorer) do + described_class.new(user: user, shared: shared, project: project) + end before do expect(restorer).to receive(:read_tree_hash) { tree_hash } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a1c38a3e668..df32545b90b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -131,23 +131,19 @@ describe Project do end context 'when creating a new project' do - it 'automatically creates a CI/CD settings row' do - project = create(:project) + let_it_be(:project) { create(:project) } + it 'automatically creates a CI/CD settings row' do expect(project.ci_cd_settings).to be_an_instance_of(ProjectCiCdSetting) expect(project.ci_cd_settings).to be_persisted end it 'automatically creates a container expiration policy row' do - project = create(:project) - expect(project.container_expiration_policy).to be_an_instance_of(ContainerExpirationPolicy) expect(project.container_expiration_policy).to be_persisted end it 'automatically creates a Pages metadata row' do - project = create(:project) - expect(project.pages_metadatum).to be_an_instance_of(ProjectPagesMetadatum) expect(project.pages_metadatum).to be_persisted end @@ -532,111 +528,114 @@ describe Project do it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) } end - describe '#to_reference_with_postfix' do - it 'returns the full path with reference_postfix' do - namespace = create(:namespace, path: 'sample-namespace') - project = create(:project, path: 'sample-project', namespace: namespace) - - expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>' - end - end + describe 'reference methods' do + let_it_be(:owner) { create(:user, name: 'Gitlab') } + let_it_be(:namespace) { create(:namespace, name: 'Sample namespace', path: 'sample-namespace', owner: owner) } + let_it_be(:project) { create(:project, name: 'Sample project', path: 'sample-project', namespace: namespace) } + let_it_be(:group) { create(:group, name: 'Group', path: 'sample-group') } + let_it_be(:another_project) { create(:project, namespace: namespace) } + let_it_be(:another_namespace_project) { create(:project, name: 'another-project') } - describe '#to_reference' do - let(:owner) { create(:user, name: 'Gitlab') } - let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } - let(:project) { create(:project, path: 'sample-project', namespace: namespace) } - let(:group) { create(:group, name: 'Group', path: 'sample-group') } + describe '#to_reference' do + it 'returns the path with reference_postfix' do + expect(project.to_reference).to eq("#{project.full_path}>") + end - context 'when nil argument' do - it 'returns nil' do - expect(project.to_reference).to be_nil + it 'returns the path with reference_postfix when arg is self' do + expect(project.to_reference(project)).to eq("#{project.full_path}>") end - end - context 'when full is true' do - it 'returns complete path to the project' do - expect(project.to_reference(full: true)).to eq 'sample-namespace/sample-project' - expect(project.to_reference(project, full: true)).to eq 'sample-namespace/sample-project' - expect(project.to_reference(group, full: true)).to eq 'sample-namespace/sample-project' + it 'returns the full_path with reference_postfix when full' do + expect(project.to_reference(full: true)).to eq("#{project.full_path}>") end - end - context 'when same project argument' do - it 'returns nil' do - expect(project.to_reference(project)).to be_nil + it 'returns the full_path with reference_postfix when cross-project' do + expect(project.to_reference(build_stubbed(:project))).to eq("#{project.full_path}>") end end - context 'when cross namespace project argument' do - let(:another_namespace_project) { create(:project, name: 'another-project') } - - it 'returns complete path to the project' do - expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project' + describe '#to_reference_base' do + context 'when nil argument' do + it 'returns nil' do + expect(project.to_reference_base).to be_nil + end end - end - context 'when same namespace / cross-project argument' do - let(:another_project) { create(:project, namespace: namespace) } + context 'when full is true' do + it 'returns complete path to the project', :aggregate_failures do + be_full_path = eq('sample-namespace/sample-project') - it 'returns path to the project' do - expect(project.to_reference(another_project)).to eq 'sample-project' + expect(project.to_reference_base(full: true)).to be_full_path + expect(project.to_reference_base(project, full: true)).to be_full_path + expect(project.to_reference_base(group, full: true)).to be_full_path + end end - end - context 'when different namespace / cross-project argument' do - let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) } - let(:another_project) { create(:project, path: 'another-project', namespace: another_namespace) } + context 'when same project argument' do + it 'returns nil' do + expect(project.to_reference_base(project)).to be_nil + end + end - it 'returns full path to the project' do - expect(project.to_reference(another_project)).to eq 'sample-namespace/sample-project' + context 'when cross namespace project argument' do + it 'returns complete path to the project' do + expect(project.to_reference_base(another_namespace_project)).to eq 'sample-namespace/sample-project' + end end - end - context 'when argument is a namespace' do - context 'with same project path' do + context 'when same namespace / cross-project argument' do it 'returns path to the project' do - expect(project.to_reference(namespace)).to eq 'sample-project' + expect(project.to_reference_base(another_project)).to eq 'sample-project' end end - context 'with different project path' do + context 'when different namespace / cross-project argument with same owner' do + let(:another_namespace_same_owner) { create(:namespace, path: 'another-namespace', owner: owner) } + let(:another_project_same_owner) { create(:project, path: 'another-project', namespace: another_namespace_same_owner) } + it 'returns full path to the project' do - expect(project.to_reference(group)).to eq 'sample-namespace/sample-project' + expect(project.to_reference_base(another_project_same_owner)).to eq 'sample-namespace/sample-project' end end - end - end - describe '#to_human_reference' do - let(:owner) { create(:user, name: 'Gitlab') } - let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) } - let(:project) { create(:project, name: 'Sample project', namespace: namespace) } + context 'when argument is a namespace' do + context 'with same project path' do + it 'returns path to the project' do + expect(project.to_reference_base(namespace)).to eq 'sample-project' + end + end - context 'when nil argument' do - it 'returns nil' do - expect(project.to_human_reference).to be_nil + context 'with different project path' do + it 'returns full path to the project' do + expect(project.to_reference_base(group)).to eq 'sample-namespace/sample-project' + end + end end end - context 'when same project argument' do - it 'returns nil' do - expect(project.to_human_reference(project)).to be_nil + describe '#to_human_reference' do + context 'when nil argument' do + it 'returns nil' do + expect(project.to_human_reference).to be_nil + end end - end - - context 'when cross namespace project argument' do - let(:another_namespace_project) { create(:project, name: 'another-project') } - it 'returns complete name with namespace of the project' do - expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project' + context 'when same project argument' do + it 'returns nil' do + expect(project.to_human_reference(project)).to be_nil + end end - end - context 'when same namespace / cross-project argument' do - let(:another_project) { create(:project, namespace: namespace) } + context 'when cross namespace project argument' do + it 'returns complete name with namespace of the project' do + expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project' + end + end - it 'returns name of the project' do - expect(project.to_human_reference(another_project)).to eq 'Sample project' + context 'when same namespace / cross-project argument' do + it 'returns name of the project' do + expect(project.to_human_reference(another_project)).to eq 'Sample project' + end end end end diff --git a/spec/presenters/sentry_detailed_error_presenter_spec.rb b/spec/presenters/sentry_error_presenter_spec.rb index e483b6d41a1..5f3f1d33b86 100644 --- a/spec/presenters/sentry_detailed_error_presenter_spec.rb +++ b/spec/presenters/sentry_error_presenter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe SentryDetailedErrorPresenter do +describe SentryErrorPresenter do let(:error) { build(:detailed_error_tracking_error) } let(:presenter) { described_class.new(error) } @@ -10,7 +10,7 @@ describe SentryDetailedErrorPresenter do subject { presenter.frequency } it 'returns an array of frequency structs' do - expect(subject).to include(a_kind_of(SentryDetailedErrorPresenter::FrequencyStruct)) + expect(subject).to include(a_kind_of(SentryErrorPresenter::FrequencyStruct)) end it 'converts the times into UTC time objects' do diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb new file mode 100644 index 00000000000..e68025bf01b --- /dev/null +++ b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb @@ -0,0 +1,191 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'sentry errors requests' do + include GraphqlHelpers + let_it_be(:project) { create(:project, :repository) } + let_it_be(:project_setting) { create(:project_error_tracking_setting, project: project) } + let_it_be(:current_user) { project.owner } + + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('sentryErrors', {}, fields) + ) + end + + describe 'getting a detailed sentry error' do + let_it_be(:sentry_detailed_error) { build(:detailed_error_tracking_error) } + let(:sentry_gid) { sentry_detailed_error.to_global_id.to_s } + + let(:detailed_fields) do + all_graphql_fields_for('SentryDetailedError'.classify) + end + + let(:fields) do + query_graphql_field('detailedError', { id: sentry_gid }, detailed_fields) + end + + let(:error_data) { graphql_data.dig('project', 'sentryErrors', 'detailedError') } + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end + + context 'when data is loading via reactive cache' do + before do + post_graphql(query, current_user: current_user) + end + + it "is expected to return an empty error" do + expect(error_data).to eq nil + end + end + + context 'reactive cache returns data' do + before do + allow_any_instance_of(ErrorTracking::ProjectErrorTrackingSetting) + .to receive(:issue_details) + .and_return({ issue: sentry_detailed_error }) + + post_graphql(query, current_user: current_user) + end + + let(:sentry_error) { sentry_detailed_error } + let(:error) { error_data } + + it_behaves_like 'setting sentry error data' + + it 'is expected to return the frequency correctly' do + aggregate_failures 'it returns the frequency correctly' do + expect(error_data['frequency'].count).to eql sentry_detailed_error.frequency.count + + first_frequency = error_data['frequency'].first + expect(Time.parse(first_frequency['time'])).to eql Time.at(sentry_detailed_error.frequency[0][0], in: 0) + expect(first_frequency['count']).to eql sentry_detailed_error.frequency[0][1] + end + end + + context 'user does not have permission' do + let(:current_user) { create(:user) } + + it "is expected to return an empty error" do + expect(error_data).to eq nil + end + end + end + + context 'sentry api returns an error' do + before do + expect_any_instance_of(ErrorTracking::ProjectErrorTrackingSetting) + .to receive(:issue_details) + .and_return({ error: 'error message' }) + + post_graphql(query, current_user: current_user) + end + + it 'is expected to handle the error and return nil' do + expect(error_data).to eq nil + end + end + end + + describe 'getting an errors list' do + let_it_be(:sentry_error) { build(:error_tracking_error) } + let_it_be(:pagination) do + { + 'next' => { 'cursor' => '2222' }, + 'previous' => { 'cursor' => '1111' } + } + end + + let(:fields) do + <<~QUERY + errors { + nodes { + #{all_graphql_fields_for('SentryError'.classify)} + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } + QUERY + end + + let(:error_data) { graphql_data.dig('project', 'sentryErrors', 'errors', 'nodes') } + let(:pagination_data) { graphql_data.dig('project', 'sentryErrors', 'errors', 'pageInfo') } + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end + + context 'when data is loading via reactive cache' do + before do + post_graphql(query, current_user: current_user) + end + + it "is expected to return nil" do + expect(error_data).to eq nil + end + end + + context 'reactive cache returns data' do + before do + expect_any_instance_of(ErrorTracking::ProjectErrorTrackingSetting) + .to receive(:list_sentry_issues) + .and_return({ issues: [sentry_error], pagination: pagination }) + + post_graphql(query, current_user: current_user) + end + + let(:error) { error_data.first } + + it 'is expected to return an array of data' do + expect(error_data).to be_a Array + expect(error_data.count).to eq 1 + end + + it_behaves_like 'setting sentry error data' + + it 'sets the pagination correctly' do + expect(pagination_data['startCursor']).to eq(pagination['previous']['cursor']) + expect(pagination_data['endCursor']).to eq(pagination['next']['cursor']) + end + + it 'is expected to return the frequency correctly' do + aggregate_failures 'it returns the frequency correctly' do + error = error_data.first + + expect(error['frequency'].count).to eql sentry_error.frequency.count + + first_frequency = error['frequency'].first + + expect(Time.parse(first_frequency['time'])).to eql Time.at(sentry_error.frequency[0][0], in: 0) + expect(first_frequency['count']).to eql sentry_error.frequency[0][1] + end + end + end + + context "sentry api itself errors out" do + before do + expect_any_instance_of(ErrorTracking::ProjectErrorTrackingSetting) + .to receive(:list_sentry_issues) + .and_return({ error: 'error message' }) + + post_graphql(query, current_user: current_user) + end + + it 'is expected to handle the error and return nil' do + expect(error_data).to eq nil + end + end + end +end diff --git a/spec/requests/self_monitoring_project_spec.rb b/spec/requests/self_monitoring_project_spec.rb index 1da0be882d0..5e46645e7a0 100644 --- a/spec/requests/self_monitoring_project_spec.rb +++ b/spec/requests/self_monitoring_project_spec.rb @@ -68,6 +68,8 @@ describe 'Self-Monitoring project requests' do let(:job_id) { nil } it 'returns bad_request' do + create(:application_setting) + subject aggregate_failures do @@ -81,11 +83,10 @@ describe 'Self-Monitoring project requests' do end context 'when self-monitoring project exists' do - let(:project) { build(:project) } + let(:project) { create(:project) } before do - stub_application_setting(self_monitoring_project_id: 1) - stub_application_setting(self_monitoring_project: project) + create(:application_setting, self_monitoring_project_id: project.id) end it 'does not need job_id' do @@ -94,7 +95,7 @@ describe 'Self-Monitoring project requests' do aggregate_failures do expect(response).to have_gitlab_http_status(:success) expect(json_response).to eq( - 'project_id' => 1, + 'project_id' => project.id, 'project_full_path' => project.full_path ) end @@ -106,7 +107,7 @@ describe 'Self-Monitoring project requests' do aggregate_failures do expect(response).to have_gitlab_http_status(:success) expect(json_response).to eq( - 'project_id' => 1, + 'project_id' => project.id, 'project_full_path' => project.full_path ) end @@ -179,7 +180,7 @@ describe 'Self-Monitoring project requests' do context 'when self-monitoring project exists and job does not exist' do before do - stub_application_setting(self_monitoring_project_id: 1) + create(:application_setting, self_monitoring_project_id: create(:project).id) end it 'returns bad_request' do @@ -196,6 +197,10 @@ describe 'Self-Monitoring project requests' do end context 'when self-monitoring project does not exist' do + before do + create(:application_setting) + end + it 'does not need job_id' do get status_delete_self_monitoring_project_admin_application_settings_path diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index e151a934591..31b0290bb15 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -108,6 +108,12 @@ RSpec::Matchers.define :have_graphql_resolver do |expected| end end +RSpec::Matchers.define :have_graphql_extension do |expected| + match do |field| + expect(field.metadata[:type_class].extensions).to include(expected) + end +end + RSpec::Matchers.define :expose_permissions_using do |expected| match do |type| permission_field = type.fields['userPermissions'] diff --git a/spec/support/shared_examples/error_tracking_shared_examples.rb b/spec/support/shared_examples/error_tracking_shared_examples.rb new file mode 100644 index 00000000000..86134fa7fd1 --- /dev/null +++ b/spec/support/shared_examples/error_tracking_shared_examples.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'setting sentry error data' do + it 'sets the sentry error data correctly' do + aggregate_failures 'testing the sentry error is correct' do + expect(error['id']).to eql sentry_error.to_global_id.to_s + expect(error['sentryId']).to eql sentry_error.id.to_s + expect(error['status']).to eql sentry_error.status.upcase + expect(error['firstSeen']).to eql sentry_error.first_seen + expect(error['lastSeen']).to eql sentry_error.last_seen + end + end +end |