diff options
Diffstat (limited to 'spec/support/shared_examples/requests/api')
8 files changed, 320 insertions, 76 deletions
diff --git a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb index 2996c794e52..5cc87fb9654 100644 --- a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb @@ -1,23 +1,29 @@ # frozen_string_literal: true RSpec.shared_examples 'with cross-reference system notes' do - let(:merge_request) { create(:merge_request) } - let(:project) { merge_request.project } - let(:new_merge_request) { create(:merge_request) } - let(:commit) { new_merge_request.project.commit } + let_it_be(:user) { create(:user) } + let_it_be(:pat) { create(:personal_access_token, user: user) } + let_it_be(:project) { create(:project, :small_repo) } + let_it_be(:project2) { create(:project, :small_repo) } + let_it_be(:project3) { create(:project, :small_repo) } + + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + let_it_be(:new_merge_request) { create(:merge_request, source_project: project2) } + let_it_be(:hidden_merge_request) { create(:merge_request, source_project: project3) } + let!(:note) { create(:system_note, noteable: merge_request, project: project, note: cross_reference) } let!(:note_metadata) { create(:system_note_metadata, note: note, action: 'cross_reference') } let(:cross_reference) { "test commit #{commit.to_reference(project)}" } - let(:pat) { create(:personal_access_token, user: user) } + let(:commit) { new_merge_request.project.commit } - before do - project.add_developer(user) - new_merge_request.project.add_developer(user) + let!(:new_note) { create(:system_note, noteable: merge_request, project: project, note: hidden_cross_reference) } + let!(:new_note_metadata) { create(:system_note_metadata, note: new_note, action: 'cross_reference') } + let(:hidden_cross_reference) { "test commit #{hidden_commit.to_reference(project)}" } + let(:hidden_commit) { hidden_merge_request.project.commit } - hidden_merge_request = create(:merge_request) - new_cross_reference = "test commit #{hidden_merge_request.project.commit.to_reference(project)}" - new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference) - create(:system_note_metadata, note: new_note, action: 'cross_reference') + before_all do + project.add_developer(user) + project2.add_developer(user) end it 'returns only the note that the user should see' do diff --git a/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb new file mode 100644 index 00000000000..beb3085a606 --- /dev/null +++ b/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'graphql work item type list request spec' do |context_name = nil| + include_context context_name || 'with work item types request context' + + context 'when user has access to the group' do + it_behaves_like 'a working graphql query that returns data' do + before do + post_graphql(query, current_user: current_user) + end + end + + it 'returns all default work item types' do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(parent_key, :workItemTypes, :nodes)).to match_array(expected_work_item_type_response) + end + + it 'prevents N+1 queries' do + # Destroy 2 existing types + WorkItems::Type.by_type([:issue, :task]).delete_all + + post_graphql(query, current_user: current_user) # warm-up + + control = ActiveRecord::QueryRecorder.new(skip_cached: false) { post_graphql(query, current_user: current_user) } + expect(graphql_errors).to be_blank + + # Add back the 2 deleted types + expect do + Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types + end.to change { WorkItems::Type.count }.by(2) + + expect { post_graphql(query, current_user: current_user) }.to issue_same_number_of_queries_as(control) + expect(graphql_errors).to be_blank + end + end + + context "when user doesn't have access to the parent" do + let(:current_user) { create(:user) } + + before do + post_graphql(query, current_user: current_user) + end + + it 'does not return the parent' do + expect(graphql_data).to eq(parent_key.to_s => nil) + end + end +end diff --git a/spec/support/shared_examples/requests/api/graphql_rest/milestones_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql_rest/milestones_shared_examples.rb new file mode 100644 index 00000000000..8e147f43091 --- /dev/null +++ b/spec/support/shared_examples/requests/api/graphql_rest/milestones_shared_examples.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# Examples for both GraphQL and REST APIs +RSpec.shared_examples 'group milestones including ancestors and descendants' do + context 'for group milestones' do + let_it_be(:current_user) { create(:user) } + + context 'when including descendant milestones in a public group' do + let_it_be(:group) { create(:group, :public) } + + let(:params) { { include_descendants: true } } + + it 'finds milestones only in accessible projects and groups' do + accessible_group = create(:group, :private, parent: group) + accessible_project = create(:project, group: accessible_group) + accessible_group.add_developer(current_user) + inaccessible_group = create(:group, :private, parent: group) + inaccessible_project = create(:project, :private, group: group) + milestone1 = create(:milestone, group: group) + milestone2 = create(:milestone, group: accessible_group) + milestone3 = create(:milestone, project: accessible_project) + create(:milestone, group: inaccessible_group) + create(:milestone, project: inaccessible_project) + + milestone_ids = query_group_milestone_ids(params) + + expect(milestone_ids).to match_array([milestone1, milestone2, milestone3].pluck(:id)) + end + end + + describe 'include_descendants and include_ancestors' do + let_it_be(:parent_group) { create(:group, :public) } + let_it_be(:group) { create(:group, :public, parent: parent_group) } + let_it_be(:accessible_group) { create(:group, :private, parent: group) } + let_it_be(:accessible_project) { create(:project, group: accessible_group) } + let_it_be(:inaccessible_group) { create(:group, :private, parent: group) } + let_it_be(:inaccessible_project) { create(:project, :private, group: group) } + let_it_be(:milestone1) { create(:milestone, group: group) } + let_it_be(:milestone2) { create(:milestone, group: accessible_group) } + let_it_be(:milestone3) { create(:milestone, project: accessible_project) } + let_it_be(:milestone4) { create(:milestone, group: inaccessible_group) } + let_it_be(:milestone5) { create(:milestone, project: inaccessible_project) } + let_it_be(:milestone6) { create(:milestone, group: parent_group) } + + before_all do + accessible_group.add_developer(current_user) + end + + context 'when including neither ancestor nor descendant milestones in a public group' do + let(:params) { {} } + + it 'finds milestones only in accessible projects and groups' do + expect(query_group_milestone_ids(params)).to match_array([milestone1.id]) + end + end + + context 'when including descendant milestones in a public group' do + let(:params) { { include_descendants: true } } + + it 'finds milestones only in accessible projects and groups' do + expect(query_group_milestone_ids(params)).to match_array([milestone1, milestone2, milestone3].pluck(:id)) + end + end + + context 'when including ancestor milestones in a public group' do + let(:params) { { include_ancestors: true } } + + it 'finds milestones only in accessible projects and groups' do + expect(query_group_milestone_ids(params)).to match_array([milestone1, milestone6].pluck(:id)) + end + end + + context 'when including both ancestor and descendant milestones in a public group' do + let(:params) { { include_descendants: true, include_ancestors: true } } + + it 'finds milestones only in accessible projects and groups' do + expect(query_group_milestone_ids(params)) + .to match_array([milestone1, milestone2, milestone3, milestone6].pluck(:id)) + end + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb index 7489dc7c1d6..de458bc87db 100644 --- a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb @@ -84,7 +84,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end end - context 'the hook has URL variables' do + context 'the hook has URL variables', if: prefix != '/projects/:id' do before do hook.update!(url_variables: { 'token' => 'supers3cret' }) end diff --git a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb index 7978f43610d..5ae0b8b10b6 100644 --- a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb @@ -1,5 +1,53 @@ # frozen_string_literal: true +RSpec.shared_examples 'MLflow|an endpoint that requires authentication' do + context 'when not authenticated' do + let(:headers) { {} } + + it "is Unauthorized" do + is_expected.to have_gitlab_http_status(:unauthorized) + end + end + + context 'when user does not have access' do + let(:access_token) { tokens[:different_user] } + + it "is Not Found" do + is_expected.to have_gitlab_http_status(:not_found) + end + end +end + +RSpec.shared_examples 'MLflow|an endpoint that requires read_model_registry' do + context 'when user does not have read_model_registry' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(current_user, :read_model_registry, project) + .and_return(false) + end + + it "is Not Found" do + is_expected.to have_gitlab_http_status(:not_found) + end + end +end + +RSpec.shared_examples 'MLflow|an endpoint that requires write_model_registry' do + context 'when user does not have read_model_registry' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(current_user, :write_model_registry, project) + .and_return(false) + end + + it "is Not Found" do + is_expected.to have_gitlab_http_status(:unauthorized) + end + end +end + RSpec.shared_examples 'MLflow|Not Found - Resource Does Not Exist' do it "is Resource Does Not Exist", :aggregate_failures do is_expected.to have_gitlab_http_status(:not_found) @@ -44,21 +92,7 @@ RSpec.shared_examples 'MLflow|Bad Request' do end RSpec.shared_examples 'MLflow|shared error cases' do - context 'when not authenticated' do - let(:headers) { {} } - - it "is Unauthorized" do - is_expected.to have_gitlab_http_status(:unauthorized) - end - end - - context 'when user does not have access' do - let(:access_token) { tokens[:different_user] } - - it "is Not Found" do - is_expected.to have_gitlab_http_status(:not_found) - end - end + it_behaves_like 'MLflow|an endpoint that requires authentication' context 'when model experiments is unavailable' do before do @@ -75,34 +109,8 @@ RSpec.shared_examples 'MLflow|shared error cases' do end RSpec.shared_examples 'MLflow|shared model registry error cases' do - context 'when not authenticated' do - let(:headers) { {} } - - it "is Unauthorized" do - is_expected.to have_gitlab_http_status(:unauthorized) - end - end - - context 'when user does not have access' do - let(:access_token) { tokens[:different_user] } - - it "is Not Found" do - is_expected.to have_gitlab_http_status(:not_found) - end - end - - context 'when model registry is unavailable' do - before do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?) - .with(current_user, :read_model_registry, project) - .and_return(false) - end - - it "is Not Found" do - is_expected.to have_gitlab_http_status(:not_found) - end - end + it_behaves_like 'MLflow|an endpoint that requires authentication' + it_behaves_like 'MLflow|an endpoint that requires read_model_registry' end RSpec.shared_examples 'MLflow|Bad Request on missing required' do |keys| @@ -110,9 +118,27 @@ RSpec.shared_examples 'MLflow|Bad Request on missing required' do |keys| context "when \"#{key}\" is missing" do let(:params) { default_params.tap { |p| p.delete(key) } } - it "is Bad Request" do - is_expected.to have_gitlab_http_status(:bad_request) - end + it_behaves_like 'MLflow|Bad Request' end end end + +RSpec.shared_examples 'MLflow|an invalid request' do + it_behaves_like 'MLflow|Bad Request' +end + +RSpec.shared_examples 'MLflow|an authenticated resource' do + it_behaves_like 'MLflow|an endpoint that requires authentication' + it_behaves_like 'MLflow|Requires read_api scope' +end + +RSpec.shared_examples 'MLflow|a read-only model registry resource' do + it_behaves_like 'MLflow|an endpoint that requires authentication' + it_behaves_like 'MLflow|an endpoint that requires read_model_registry' +end + +RSpec.shared_examples 'MLflow|a read/write model registry resource' do + it_behaves_like 'MLflow|an endpoint that requires authentication' + it_behaves_like 'MLflow|an endpoint that requires read_model_registry' + it_behaves_like 'MLflow|an endpoint that requires write_model_registry' +end diff --git a/spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb index 47cbd268a65..30a1398bf94 100644 --- a/spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb @@ -15,11 +15,31 @@ RSpec.shared_examples 'Endpoint not found if read_model_registry not available' end end -RSpec.shared_examples 'creates model experiments package files' do +RSpec.shared_examples 'Endpoint not found if write_model_registry not available' do + context 'when write_model_registry is disabled for current project' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(user, :write_model_registry, project) + .and_return(false) + end + + it { is_expected.to have_gitlab_http_status(:not_found) } + end +end + +RSpec.shared_examples 'Not found when model version does not exist' do + context 'when model version does not exist' do + let(:version) { "#{non_existing_record_id}.0.0" } + + it { is_expected.to have_gitlab_http_status(:not_found) } + end +end + +RSpec.shared_examples 'creates package files for model versions' do it 'creates package files', :aggregate_failures do expect { api_response } - .to change { project.packages.count }.by(1) - .and change { Packages::PackageFile.count }.by(1) + .to change { Packages::PackageFile.count }.by(1) expect(api_response).to have_gitlab_http_status(:created) package_file = project.packages.last.package_files.reload.last @@ -59,7 +79,7 @@ RSpec.shared_examples 'process ml model package upload' do context 'with correct params' do it_behaves_like 'package workhorse uploads' - it_behaves_like 'creates model experiments package files' + it_behaves_like 'creates package files for model versions' # To be reactivated with https://gitlab.com/gitlab-org/gitlab/-/issues/414270 # it_behaves_like 'a package tracking event', '::API::MlModelPackages', 'push_package' end @@ -81,7 +101,7 @@ RSpec.shared_examples 'process ml model package upload' do stub_package_file_object_storage(direct_upload: true) end - it_behaves_like 'creates model experiments package files' + it_behaves_like 'creates package files for model versions' ['123123', '../../123123'].each do |remote_id| context "with invalid remote_id: #{remote_id}" do @@ -102,7 +122,7 @@ RSpec.shared_examples 'process ml model package upload' do stub_package_file_object_storage(direct_upload: false) end - it_behaves_like 'creates model experiments package files' + it_behaves_like 'creates package files for model versions' end end end @@ -112,13 +132,5 @@ RSpec.shared_examples 'process ml model package download' do it { is_expected.to have_gitlab_http_status(:success) } end - context 'when record does not exist' do - it 'response is not found' do - expect_next_instance_of(::Packages::MlModel::PackageFinder) do |instance| - expect(instance).to receive(:execute!).and_raise(ActiveRecord::RecordNotFound) - end - - expect(api_response).to have_gitlab_http_status(:not_found) - end - end + it_behaves_like 'Not found when model version does not exist' end diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb index c23d514abfc..8281b7d4024 100644 --- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb @@ -741,3 +741,71 @@ RSpec.shared_examples 'process nuget delete request' do |user_type, status| end end end + +RSpec.shared_examples 'nuget symbol file endpoint' do + let_it_be(:symbol) { create(:nuget_symbol) } + let_it_be(:filename) { symbol.file.filename } + let_it_be(:signature) { symbol.signature } + let_it_be(:checksum) { symbol.file_sha256.delete("\n") } + + let(:headers) { { 'Symbolchecksum' => "SHA256:#{checksum}" } } + + subject { get api(url), headers: headers } + + it { is_expected.to have_request_urgency(:low) } + + context 'with nuget_symbol_server_enabled setting enabled' do + before do + allow_next_instance_of(::Namespace::PackageSetting) do |setting| + allow(setting).to receive(:nuget_symbol_server_enabled).and_return(true) + end + end + + context 'with valid target' do + it 'returns the symbol file' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type).to eq('application/octet-stream') + expect(response.body).to eq(symbol.file.read) + end + end + + context 'when target does not exist' do + let(:target) { double(id: 1234567890) } + + it_behaves_like 'returning response status', :not_found + end + + context 'when target exists' do + context 'when symbol file does not exist' do + let(:filename) { 'non-existent-file.pdb' } + let(:signature) { 'non-existent-signature' } + + it_behaves_like 'returning response status', :not_found + end + + context 'when symbol file checksum does not match' do + let(:checksum) { 'non-matching-checksum' } + + it_behaves_like 'returning response status', :not_found + end + + context 'when symbol file checksum is missing' do + let(:headers) { {} } + + it_behaves_like 'returning response status', :bad_request + end + end + end + + context 'with nuget_symbol_server_enabled setting disabled' do + before do + allow_next_instance_of(::Namespace::PackageSetting) do |setting| + allow(setting).to receive(:nuget_symbol_server_enabled).and_return(false) + end + end + + it_behaves_like 'returning response status', :forbidden + end +end diff --git a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb index 181bab41e09..f400c5fa201 100644 --- a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb @@ -90,7 +90,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| end before do - stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/extra_storage' }) + stub_storage_settings('test_second_storage' => {}) end it 'schedules a container repository storage move' do @@ -203,7 +203,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| end before do - stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/extra_storage' }) + stub_storage_settings('test_second_storage' => {}) end it 'schedules the worker' do |