diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-02 18:09:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-02 18:09:54 +0300 |
commit | d944f09d3212ca8aad09b92dbbae7323ee634237 (patch) | |
tree | bd2af8e867fb1baf2c2687fd12adab26515f3230 /spec | |
parent | ae9f43a2c4bda0ee7dae59ea9a7d412068f6f7ff (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
13 files changed, 536 insertions, 64 deletions
diff --git a/spec/initializers/google_api_client_spec.rb b/spec/initializers/google_api_client_spec.rb index 0ed82d7debe..b3c4ac5e23b 100644 --- a/spec/initializers/google_api_client_spec.rb +++ b/spec/initializers/google_api_client_spec.rb @@ -26,8 +26,9 @@ RSpec.describe Google::Apis::Core::HttpCommand do # rubocop:disable RSpec/FilePa it 'retries with max elapsed_time and retries' do expect(Retriable).to receive(:retriable).with( tries: Google::Apis::RequestOptions.default.retries + 1, - max_elapsed_time: 3600, + max_elapsed_time: 900, base_interval: 1, + max_interval: 60, multiplier: 2, on: described_class::RETRIABLE_ERRORS).and_call_original allow(Retriable).to receive(:retriable).and_call_original diff --git a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb index 02ac8065c9f..d8441a7aa30 100644 --- a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb +++ b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do +RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, feature_category: :importers do let_it_be(:user) { create(:user) } let_it_be(:release) { create(:release) } let_it_be(:group) { create(:group) } @@ -213,59 +213,143 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do end end - describe 'conditional export of included associations' do + describe 'with inaccessible associations' do + let_it_be(:milestone) { create(:milestone, project: exportable) } + let_it_be(:issue) { create(:issue, assignees: [user], project: exportable, milestone: milestone) } + let_it_be(:label1) { create(:label, project: exportable) } + let_it_be(:label2) { create(:label, project: exportable) } + let_it_be(:link1) { create(:label_link, label: label1, target: issue) } + let_it_be(:link2) { create(:label_link, label: label2, target: issue) } + + let(:options) { { include: [{ label_links: { include: [:label] } }, { milestone: { include: [] } }] } } + let(:include) do - [{ issues: { include: [{ label_links: { include: [:label] } }] } }] + [{ issues: options }] end - let(:include_if_exportable) do - { issues: [:label_links] } + shared_examples 'record with exportable associations' do + it 'includes exportable association' do + expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, array_including(expected_issue)) + + subject.execute + end end - let_it_be(:label) { create(:label, project: exportable) } - let_it_be(:link) { create(:label_link, label: label, target: issue) } + context 'conditional export of included associations' do + let(:include_if_exportable) do + { issues: [:label_links, :milestone] } + end - context 'when association is exportable' do - before do - allow_next_found_instance_of(Issue) do |issue| - allow(issue).to receive(:exportable_association?).with(:label_links, current_user: user).and_return(true) + context 'when association is exportable' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:exportable_association?).with(:label_links, current_user: user).and_return(true) + allow(issue).to receive(:exportable_association?).with(:milestone, current_user: user).and_return(true) + end + end + + it_behaves_like 'record with exportable associations' do + let(:expected_issue) { issue.to_json(options) } end end - it 'includes exportable association' do - expected_issue = issue.to_json(include: [{ label_links: { include: [:label] } }]) + context 'when an association is not exportable' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:exportable_association?).with(:label_links, current_user: user).and_return(true) + allow(issue).to receive(:exportable_association?).with(:milestone, current_user: user).and_return(false) + end + end - expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, array_including(expected_issue)) + it_behaves_like 'record with exportable associations' do + let(:expected_issue) { issue.to_json(include: [{ label_links: { include: [:label] } }]) } + end + end - subject.execute + context 'when association does not respond to exportable_association?' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:respond_to?).and_call_original + allow(issue).to receive(:respond_to?).with(:exportable_association?).and_return(false) + end + end + + it_behaves_like 'record with exportable associations' do + let(:expected_issue) { issue.to_json } + end end end - context 'when association is not exportable' do - before do - allow_next_found_instance_of(Issue) do |issue| - allow(issue).to receive(:exportable_association?).with(:label_links, current_user: user).and_return(false) + context 'export of included restricted associations' do + let(:many_relation) { :label_links } + let(:single_relation) { :milestone } + let(:issue_hash) { issue.as_json(options).with_indifferent_access } + let(:expected_issue) { issue.to_json(options) } + + context 'when the association is restricted' do + context 'when some association records are exportable' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:restricted_associations).with([many_relation, single_relation]).and_return([many_relation]) + allow(issue).to receive(:readable_records).with(many_relation, current_user: user).and_return([link1]) + end + end + + it_behaves_like 'record with exportable associations' do + let(:expected_issue) do + issue_hash[many_relation].delete_at(1) + issue_hash.to_json(options) + end + end end - end - it 'filters out not exportable association' do - expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, array_including(issue.to_json)) + context 'when all association records are exportable' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:restricted_associations).with([many_relation, single_relation]).and_return([many_relation]) + allow(issue).to receive(:readable_records).with(many_relation, current_user: user).and_return([link1, link2]) + end + end - subject.execute - end - end + it_behaves_like 'record with exportable associations' + end - context 'when association does not respond to exportable_association?' do - before do - allow_next_found_instance_of(Issue) do |issue| - allow(issue).to receive(:respond_to?).with(:exportable_association?).and_return(false) + context 'when the single association record is exportable' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:restricted_associations).with([many_relation, single_relation]).and_return([single_relation]) + allow(issue).to receive(:readable_records).with(single_relation, current_user: user).and_return(milestone) + end + end + + it_behaves_like 'record with exportable associations' + end + + context 'when the single association record is not exportable' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:restricted_associations).with([many_relation, single_relation]).and_return([single_relation]) + allow(issue).to receive(:readable_records).with(single_relation, current_user: user).and_return(nil) + end + end + + it_behaves_like 'record with exportable associations' do + let(:expected_issue) do + issue_hash[single_relation] = nil + issue_hash.to_json(options) + end + end end end - it 'filters out not exportable association' do - expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, array_including(issue.to_json)) + context 'when the associations are not restricted' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:restricted_associations).with([many_relation, single_relation]).and_return([]) + end + end - subject.execute + it_behaves_like 'record with exportable associations' end end end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_internal_pipelines_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_internal_pipelines_metric_spec.rb new file mode 100644 index 00000000000..afd8fccd56c --- /dev/null +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_internal_pipelines_metric_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCiInternalPipelinesMetric, +feature_category: :service_ping do + let_it_be(:ci_pipeline_1) { create(:ci_pipeline, source: :external) } + let_it_be(:ci_pipeline_2) { create(:ci_pipeline, source: :push) } + + let(:expected_value) { 1 } + let(:expected_query) do + 'SELECT COUNT("ci_pipelines"."id") FROM "ci_pipelines" ' \ + 'WHERE ("ci_pipelines"."source" IN (1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15) ' \ + 'OR "ci_pipelines"."source" IS NULL)' + end + + it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' } + + context 'on Gitlab.com' do + before do + allow(Gitlab).to receive(:com?).and_return(true) + end + + let(:expected_value) { -1 } + + it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' } + end +end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_created_manually_from_alerts_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_created_manually_from_alerts_metric_spec.rb new file mode 100644 index 00000000000..86f54c48666 --- /dev/null +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_created_manually_from_alerts_metric_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountIssuesCreatedManuallyFromAlertsMetric, +feature_category: :service_ping do + let_it_be(:issue) { create(:issue) } + let_it_be(:issue_with_alert) { create(:issue, :with_alert) } + + let(:expected_value) { 1 } + let(:expected_query) do + 'SELECT COUNT("issues"."id") FROM "issues" ' \ + 'INNER JOIN "alert_management_alerts" ON "alert_management_alerts"."issue_id" = "issues"."id" ' \ + 'WHERE "issues"."author_id" != 99' + end + + it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' } + + context 'on Gitlab.com' do + before do + allow(Gitlab).to receive(:com?).and_return(true) + end + + let(:expected_value) { -1 } + + it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' } + end +end diff --git a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb index 83a4ea8e948..4f647c2700a 100644 --- a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb +++ b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do +RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator, feature_category: :service_ping do include UsageDataHelpers before do @@ -43,9 +43,9 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do context 'joined relations' do context 'counted attribute comes from source relation' do it_behaves_like 'name suggestion' do - # corresponding metric is collected with count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id) - let(:key_path) { 'counts.issues_created_manually_from_alerts' } - let(:name_suggestion) { /count_<adjective describing: '\(issues\.author_id != \d+\)'>_issues_<with>_alert_management_alerts/ } + # corresponding metric is collected with distinct_count(Release.with_milestones, :author_id) + let(:key_path) { 'usage_activity_by_stage.release.releases_with_milestones' } + let(:name_suggestion) { /count_distinct_author_id_from_releases_<with>_milestone_releases/ } end end end diff --git a/spec/lib/gitlab/usage/service_ping_report_spec.rb b/spec/lib/gitlab/usage/service_ping_report_spec.rb index ee2469ea463..730c05b7dcb 100644 --- a/spec/lib/gitlab/usage/service_ping_report_spec.rb +++ b/spec/lib/gitlab/usage/service_ping_report_spec.rb @@ -171,14 +171,25 @@ RSpec.describe Gitlab::Usage::ServicePingReport, :use_clean_rails_memory_store_c let(:metric_definitions) { ::Gitlab::Usage::MetricDefinition.definitions } it 'generates queries that match collected data', :aggregate_failures do - message = "Expected %{query} result to match %{value} for %{key_path} metric" + message = "Expected %{query} result to match %{value} for %{key_path} metric (got %{payload_value} instead)" metrics_queries_with_values.each do |key_path, query, value| - value = type_cast_to_defined_type(value, metric_definitions[key_path.join('.')]) + metric_definition = metric_definitions[key_path.join('.')] + + # Skip broken metrics since they are usually overriden to return -1 + next if metric_definition&.attributes&.fetch(:status) == 'broken' + + value = type_cast_to_defined_type(value, metric_definition) + payload_value = service_ping_payload.dig(*key_path) expect(value).to( - eq(service_ping_payload.dig(*key_path)), - message % { query: query, value: (value || 'NULL'), key_path: key_path.join('.') } + eq(payload_value), + message % { + query: query, + value: (value || 'NULL'), + payload_value: payload_value, + key_path: key_path.join('.') + } ) end end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 0b2c0f9170e..ffa34acef5c 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -557,9 +557,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic expect(count_data[:issues_using_zoom_quick_actions]).to eq(3) expect(count_data[:issues_with_embedded_grafana_charts_approx]).to eq(2) expect(count_data[:incident_issues]).to eq(4) - expect(count_data[:issues_created_gitlab_alerts]).to eq(1) expect(count_data[:issues_created_from_alerts]).to eq(3) - expect(count_data[:issues_created_manually_from_alerts]).to eq(1) expect(count_data[:alert_bot_incident_issues]).to eq(4) expect(count_data[:clusters_enabled]).to eq(6) expect(count_data[:project_clusters_enabled]).to eq(4) @@ -1137,20 +1135,4 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic expect(result).to be_nil end end - - context 'on Gitlab.com' do - before do - allow(Gitlab).to receive(:com?).and_return(true) - end - - describe '.system_usage_data' do - subject { described_class.system_usage_data } - - it 'returns fallback value for disabled metrics' do - expect(subject[:counts][:ci_internal_pipelines]).to eq(Gitlab::Utils::UsageData::FALLBACK) - expect(subject[:counts][:issues_created_gitlab_alerts]).to eq(Gitlab::Utils::UsageData::FALLBACK) - expect(subject[:counts][:issues_created_manually_from_alerts]).to eq(Gitlab::Utils::UsageData::FALLBACK) - end - end - end end diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb index 1fd2a92866d..f5fce559778 100644 --- a/spec/mailers/emails/profile_spec.rb +++ b/spec/mailers/emails/profile_spec.rb @@ -438,7 +438,7 @@ RSpec.describe Emails::Profile do end it 'includes a link to the change password documentation' do - is_expected.to have_body_text 'https://docs.gitlab.com/ee/user/profile/user_passwords.html#change-your-password' + is_expected.to have_body_text help_page_url('user/profile/user_passwords', anchor: 'change-your-password') end it 'mentions two factor authentication when two factor is not enabled' do @@ -446,7 +446,7 @@ RSpec.describe Emails::Profile do end it 'includes a link to two-factor authentication documentation' do - is_expected.to have_body_text 'https://docs.gitlab.com/ee/user/profile/account/two_factor_authentication.html' + is_expected.to have_body_text help_page_url('user/profile/account/two_factor_authentication') end context 'when two factor authentication is enabled' do @@ -488,7 +488,7 @@ RSpec.describe Emails::Profile do end it 'includes a link to the change password documentation' do - is_expected.to have_body_text 'https://docs.gitlab.com/ee/user/profile/user_passwords.html#change-your-password' + is_expected.to have_body_text help_page_url('user/profile/user_passwords', anchor: 'change-your-password') end end diff --git a/spec/models/concerns/exportable_spec.rb b/spec/models/concerns/exportable_spec.rb new file mode 100644 index 00000000000..74709b06403 --- /dev/null +++ b/spec/models/concerns/exportable_spec.rb @@ -0,0 +1,236 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Exportable, feature_category: :importers do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:milestone) { create(:milestone, project: project) } + let_it_be(:issue) { create(:issue, project: project, milestone: milestone) } + let_it_be(:note1) { create(:system_note, project: project, noteable: issue) } + let_it_be(:note2) { create(:system_note, project: project, noteable: issue) } + + let_it_be(:model_klass) do + Class.new(ApplicationRecord) do + include Exportable + + belongs_to :project + has_one :milestone + has_many :notes + + self.table_name = 'issues' + + def self.name + 'Issue' + end + end + end + + subject { model_klass.new } + + describe '.readable_records' do + let_it_be(:model_record) { model_klass.new } + + context 'when model does not respond to association name' do + it 'returns nil' do + expect(subject.readable_records(:foo, current_user: user)).to be_nil + end + end + + context 'when model does respond to association name' do + context 'when there are no records' do + it 'returns nil' do + expect(model_record.readable_records(:notes, current_user: user)).to be_nil + end + end + + context 'when association has #exportable_record? defined' do + before do + allow(model_record).to receive(:try).with(:notes).and_return(issue.notes) + end + + context 'when user can read all records' do + before do + allow_next_found_instance_of(Note) do |note| + allow(note).to receive(:respond_to?).with(:exportable_record?).and_return(true) + allow(note).to receive(:exportable_record?).with(user).and_return(true) + end + end + + it 'returns collection of readable records' do + expect(model_record.readable_records(:notes, current_user: user)).to contain_exactly(note1, note2) + end + end + + context 'when user can not read records' do + before do + allow_next_instance_of(Note) do |note| + allow(note).to receive(:respond_to?).with(:exportable_record?).and_return(true) + allow(note).to receive(:exportable_record?).with(user).and_return(false) + end + end + + it 'returns collection of readable records' do + expect(model_record.readable_records(:notes, current_user: user)).to eq([]) + end + end + end + + context 'when association does not have #exportable_record? defined' do + before do + allow(model_record).to receive(:try).with(:notes).and_return([note1]) + + allow(note1).to receive(:respond_to?).and_call_original + allow(note1).to receive(:respond_to?).with(:exportable_record?).and_return(false) + end + + it 'calls #readable_by?' do + expect(note1).to receive(:readable_by?).with(user) + + model_record.readable_records(:notes, current_user: user) + end + end + + context 'with single relation' do + before do + allow(model_record).to receive(:try).with(:milestone).and_return(issue.milestone) + end + + context 'when user can read the record' do + before do + allow(milestone).to receive(:readable_by?).with(user).and_return(true) + end + + it 'returns collection of readable records' do + expect(model_record.readable_records(:milestone, current_user: user)).to eq(milestone) + end + end + + context 'when user can not read the record' do + before do + allow(milestone).to receive(:readable_by?).with(user).and_return(false) + end + + it 'returns collection of readable records' do + expect(model_record.readable_records(:milestone, current_user: user)).to be_nil + end + end + end + end + end + + describe '.exportable_association?' do + context 'when model does not respond to association name' do + it 'returns false' do + expect(subject.exportable_association?(:tests)).to eq(false) + + allow(issue).to receive(:respond_to?).with(:tests).and_return(false) + end + end + + context 'when model responds to association name' do + let_it_be(:model_record) { model_klass.new } + + context 'when association contains records' do + before do + allow(model_record).to receive(:try).with(:milestone).and_return(milestone) + end + + context 'when current_user is not present' do + it 'returns false' do + expect(model_record.exportable_association?(:milestone)).to eq(false) + end + end + + context 'when current_user can read association' do + before do + allow(milestone).to receive(:readable_by?).with(user).and_return(true) + end + + it 'returns true' do + expect(model_record.exportable_association?(:milestone, current_user: user)).to eq(true) + end + end + + context 'when current_user can not read association' do + before do + allow(milestone).to receive(:readable_by?).with(user).and_return(false) + end + + it 'returns false' do + expect(model_record.exportable_association?(:milestone, current_user: user)).to eq(false) + end + end + end + + context 'when association is empty' do + before do + allow(model_record).to receive(:try).with(:milestone).and_return(nil) + allow(milestone).to receive(:readable_by?).with(user).and_return(true) + end + + it 'returns true' do + expect(model_record.exportable_association?(:milestone, current_user: user)).to eq(true) + end + end + + context 'when association type is has_many' do + it 'returns true' do + expect(subject.exportable_association?(:notes)).to eq(true) + end + end + end + end + + describe '.restricted_associations' do + let(:model_associations) { [:notes, :labels] } + + context 'when `exportable_restricted_associations` is not defined in inheriting class' do + it 'returns empty array' do + expect(subject.restricted_associations(model_associations)).to eq([]) + end + end + + context 'when `exportable_restricted_associations` is defined in inheriting class' do + before do + stub_const('DummyModel', model_klass) + + DummyModel.class_eval do + def exportable_restricted_associations + super + [:notes] + end + end + end + + it 'returns empty array if provided key are not restricted' do + expect(subject.restricted_associations([:labels])).to eq([]) + end + + it 'returns array with restricted keys' do + expect(subject.restricted_associations(model_associations)).to contain_exactly(:notes) + end + end + end + + describe '.has_many_association?' do + let(:model_associations) { [:notes, :labels] } + + context 'when association type is `has_many`' do + it 'returns true' do + expect(subject.has_many_association?(:notes)).to eq(true) + end + end + + context 'when association type is `has_one`' do + it 'returns true' do + expect(subject.has_many_association?(:milestone)).to eq(false) + end + end + + context 'when association type is `belongs_to`' do + it 'returns true' do + expect(subject.has_many_association?(:project)).to eq(false) + end + end + end +end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 27a4132c27e..aa284f34c2f 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -1878,4 +1878,34 @@ RSpec.describe Note do it { is_expected.to eq :read_internal_note } end end + + describe '#exportable_record?' do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :private) } + let_it_be(:noteable) { create(:issue, project: project) } + + subject { note.exportable_record?(user) } + + context 'when not a system note' do + let(:note) { build(:note, noteable: noteable) } + + it { is_expected.to be_truthy } + end + + context 'with system note' do + let(:note) { build(:system_note, project: project, noteable: noteable) } + + it 'returns `false` when the user cannot read the note' do + is_expected.to be_falsey + end + + context 'when user can read the note' do + before do + project.add_developer(user) + end + + it { is_expected.to be_truthy } + end + end + end end diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index aadc7dfb69d..2bec945fbc8 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -4,7 +4,6 @@ module UsageDataHelpers COUNTS_KEYS = %i( assignee_lists ci_builds - ci_internal_pipelines ci_external_pipelines ci_pipeline_config_auto_devops ci_pipeline_config_repository diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb index 6d0462a9ee8..fe880e4b31b 100644 --- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb @@ -33,7 +33,7 @@ RSpec.shared_examples "chat integration" do |integration_name| describe "#execute" do let_it_be(:user) { create(:user) } - let_it_be_with_reload(:project) { create(:project, :repository) } + let_it_be_with_refind(:project) { create(:project, :repository) } let(:webhook_url) { "https://example.gitlab.com/" } let(:webhook_url_regex) { /\A#{webhook_url}.*/ } diff --git a/spec/support/shared_examples/models/exportable_shared_examples.rb b/spec/support/shared_examples/models/exportable_shared_examples.rb new file mode 100644 index 00000000000..37c3e68fd5f --- /dev/null +++ b/spec/support/shared_examples/models/exportable_shared_examples.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'resource with exportable associations' do + before do + stub_licensed_features(stubbed_features) if stubbed_features.any? + end + + describe '#exportable_association?' do + let(:association) { single_association } + + subject { resource.exportable_association?(association, current_user: user) } + + it { is_expected.to be_falsey } + + context 'when user can read resource' do + before do + group.add_developer(user) + end + + it { is_expected.to be_falsey } + + context "when user can read resource's association" do + before do + other_group.add_developer(user) + end + + it { is_expected.to be_truthy } + + context 'for an unknown association' do + let(:association) { :foo } + + it { is_expected.to be_falsey } + end + + context 'for an unauthenticated user' do + let(:user) { nil } + + it { is_expected.to be_falsey } + end + end + end + end + + describe '#readable_records' do + subject { resource.readable_records(association, current_user: user) } + + before do + group.add_developer(user) + end + + context 'when association not supported' do + let(:association) { :foo } + + it { is_expected.to be_nil } + end + + context 'when association is `:notes`' do + let(:association) { :notes } + + it { is_expected.to match_array([readable_note]) } + + context 'when user have access' do + before do + other_group.add_developer(user) + end + + it 'returns all records' do + is_expected.to match_array([readable_note, restricted_note]) + end + end + end + end +end |