diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-20 15:19:03 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-20 15:19:03 +0000 |
commit | 14bd84b61276ef29b97d23642d698de769bacfd2 (patch) | |
tree | f9eba90140c1bd874211dea17750a0d422c04080 /spec/requests/api | |
parent | 891c388697b2db0d8ee0c8358a9bdbf6dc56d581 (diff) | |
download | gitlab-ce-14bd84b61276ef29b97d23642d698de769bacfd2.tar.gz |
Add latest changes from gitlab-org/gitlab@15-10-stable-eev15.10.0-rc42
Diffstat (limited to 'spec/requests/api')
131 files changed, 3605 insertions, 1507 deletions
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index 8c14ead9e42..45d1594c734 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::AccessRequests, feature_category: :authentication_and_authorization do +RSpec.describe API::AccessRequests, feature_category: :system_access do let_it_be(:maintainer) { create(:user) } let_it_be(:developer) { create(:user) } let_it_be(:access_requester) { create(:user) } diff --git a/spec/requests/api/admin/batched_background_migrations_spec.rb b/spec/requests/api/admin/batched_background_migrations_spec.rb index d946ac17f3f..e88fba3fbe7 100644 --- a/spec/requests/api/admin/batched_background_migrations_spec.rb +++ b/spec/requests/api/admin/batched_background_migrations_spec.rb @@ -4,22 +4,23 @@ require 'spec_helper' RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :database do let(:admin) { create(:admin) } - let(:unauthorized_user) { create(:user) } describe 'GET /admin/batched_background_migrations/:id' do let!(:migration) { create(:batched_background_migration, :paused) } let(:database) { :main } let(:params) { { database: database } } + let(:path) { "/admin/batched_background_migrations/#{migration.id}" } + + it_behaves_like "GET request permissions for admin mode" subject(:show_migration) do - get api("/admin/batched_background_migrations/#{migration.id}", admin), params: { database: database } + get api(path, admin, admin_mode: true), params: { database: database } end it 'fetches the batched background migration' do show_migration aggregate_failures "testing response" do - expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq(migration.id) expect(json_response['status']).to eq('paused') expect(json_response['job_class_name']).to eq(migration.job_class_name) @@ -29,7 +30,8 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab context 'when the batched background migration does not exist' do it 'returns 404' do - get api("/admin/batched_background_migrations/#{non_existing_record_id}", admin), params: params + get api("/admin/batched_background_migrations/#{non_existing_record_id}", admin, admin_mode: true), + params: params expect(response).to have_gitlab_http_status(:not_found) end @@ -50,19 +52,11 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab end end - context 'when authenticated as a non-admin user' do - it 'returns 403' do - get api("/admin/batched_background_migrations/#{migration.id}", unauthorized_user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - context 'when the database name does not exist' do let(:database) { :wrong_database } - it 'returns bad request' do - get api("/admin/batched_background_migrations/#{migration.id}", admin), params: params + it 'returns bad request', :aggregate_failures do + get api(path, admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:bad_request) expect(response.body).to include('database does not have a valid value') @@ -72,13 +66,15 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab describe 'GET /admin/batched_background_migrations' do let!(:migration) { create(:batched_background_migration) } + let(:path) { '/admin/batched_background_migrations' } + + it_behaves_like "GET request permissions for admin mode" context 'when is an admin user' do it 'returns batched background migrations' do - get api('/admin/batched_background_migrations', admin) + get api(path, admin, admin_mode: true) aggregate_failures "testing response" do - expect(response).to have_gitlab_http_status(:ok) expect(json_response.count).to eq(1) expect(json_response.first['id']).to eq(migration.id) expect(json_response.first['job_class_name']).to eq(migration.job_class_name) @@ -105,14 +101,14 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(ci_model.connection).and_yield - get api('/admin/batched_background_migrations', admin), params: params + get api(path, admin, admin_mode: true), params: params end context 'when the database name does not exist' do let(:database) { :wrong_database } - it 'returns bad request' do - get api("/admin/batched_background_migrations", admin), params: params + it 'returns bad request', :aggregate_failures do + get api(path, admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:bad_request) expect(response.body).to include('database does not have a valid value') @@ -127,10 +123,9 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab create(:batched_background_migration, :active, gitlab_schema: schema) end - get api('/admin/batched_background_migrations', admin), params: params + get api(path, admin, admin_mode: true), params: params aggregate_failures "testing response" do - expect(response).to have_gitlab_http_status(:ok) expect(json_response.count).to eq(1) expect(json_response.first['id']).to eq(ci_database_migration.id) expect(json_response.first['job_class_name']).to eq(ci_database_migration.job_class_name) @@ -142,30 +137,24 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab end end end - - context 'when authenticated as a non-admin user' do - it 'returns 403' do - get api('/admin/batched_background_migrations', unauthorized_user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end end describe 'PUT /admin/batched_background_migrations/:id/resume' do let!(:migration) { create(:batched_background_migration, :paused) } let(:database) { :main } let(:params) { { database: database } } + let(:path) { "/admin/batched_background_migrations/#{migration.id}/resume" } + + it_behaves_like "PUT request permissions for admin mode" subject(:resume) do - put api("/admin/batched_background_migrations/#{migration.id}/resume", admin), params: params + put api(path, admin, admin_mode: true), params: params end it 'pauses the batched background migration' do resume aggregate_failures "testing response" do - expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq(migration.id) expect(json_response['status']).to eq('active') end @@ -173,7 +162,8 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab context 'when the batched background migration does not exist' do it 'returns 404' do - put api("/admin/batched_background_migrations/#{non_existing_record_id}/resume", admin), params: params + put api("/admin/batched_background_migrations/#{non_existing_record_id}/resume", admin, admin_mode: true), + params: params expect(response).to have_gitlab_http_status(:not_found) end @@ -183,7 +173,7 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab let!(:migration) { create(:batched_background_migration, :failed) } it 'returns 422' do - put api("/admin/batched_background_migrations/#{migration.id}/resume", admin), params: params + put api(path, admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:unprocessable_entity) end @@ -206,34 +196,28 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab context 'when the database name does not exist' do let(:database) { :wrong_database } - it 'returns bad request' do - put api("/admin/batched_background_migrations/#{migration.id}/resume", admin), params: params + it 'returns bad request', :aggregate_failures do + put api(path, admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:bad_request) expect(response.body).to include('database does not have a valid value') end end end - - context 'when authenticated as a non-admin user' do - it 'returns 403' do - put api("/admin/batched_background_migrations/#{migration.id}/resume", unauthorized_user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end end describe 'PUT /admin/batched_background_migrations/:id/pause' do let!(:migration) { create(:batched_background_migration, :active) } let(:database) { :main } let(:params) { { database: database } } + let(:path) { "/admin/batched_background_migrations/#{migration.id}/pause" } + + it_behaves_like "PUT request permissions for admin mode" it 'pauses the batched background migration' do - put api("/admin/batched_background_migrations/#{migration.id}/pause", admin), params: params + put api(path, admin, admin_mode: true), params: params aggregate_failures "testing response" do - expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq(migration.id) expect(json_response['status']).to eq('paused') end @@ -241,7 +225,8 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab context 'when the batched background migration does not exist' do it 'returns 404' do - put api("/admin/batched_background_migrations/#{non_existing_record_id}/pause", admin), params: params + put api("/admin/batched_background_migrations/#{non_existing_record_id}/pause", admin, admin_mode: true), + params: params expect(response).to have_gitlab_http_status(:not_found) end @@ -251,7 +236,7 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab let!(:migration) { create(:batched_background_migration, :failed) } it 'returns 422' do - put api("/admin/batched_background_migrations/#{migration.id}/pause", admin), params: params + put api(path, admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:unprocessable_entity) end @@ -268,27 +253,19 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab it 'uses the correct connection' do expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(ci_model.connection).and_yield - put api("/admin/batched_background_migrations/#{migration.id}/pause", admin), params: params + put api(path, admin, admin_mode: true), params: params end context 'when the database name does not exist' do let(:database) { :wrong_database } - it 'returns bad request' do - put api("/admin/batched_background_migrations/#{migration.id}/pause", admin), params: params + it 'returns bad request', :aggregate_failures do + put api(path, admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:bad_request) expect(response.body).to include('database does not have a valid value') end end end - - context 'when authenticated as a non-admin user' do - it 'returns 403' do - put api("/admin/batched_background_migrations/#{non_existing_record_id}/pause", unauthorized_user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end end end diff --git a/spec/requests/api/admin/ci/variables_spec.rb b/spec/requests/api/admin/ci/variables_spec.rb index 4bdc44cb583..dd4171b257a 100644 --- a/spec/requests/api/admin/ci/variables_spec.rb +++ b/spec/requests/api/admin/ci/variables_spec.rb @@ -5,68 +5,60 @@ require 'spec_helper' RSpec.describe ::API::Admin::Ci::Variables do let_it_be(:admin) { create(:admin) } let_it_be(:user) { create(:user) } + let_it_be(:variable) { create(:ci_instance_variable) } + let_it_be(:path) { '/admin/ci/variables' } describe 'GET /admin/ci/variables' do - let!(:variable) { create(:ci_instance_variable) } + it_behaves_like 'GET request permissions for admin mode' it 'returns instance-level variables for admins', :aggregate_failures do - get api('/admin/ci/variables', admin) + get api(path, admin, admin_mode: true) - expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_a(Array) end - it 'does not return instance-level variables for regular users' do - get api('/admin/ci/variables', user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - it 'does not return instance-level variables for unauthorized users' do - get api('/admin/ci/variables') + get api(path, admin_mode: true) expect(response).to have_gitlab_http_status(:unauthorized) end end describe 'GET /admin/ci/variables/:key' do - let!(:variable) { create(:ci_instance_variable) } + let_it_be(:path) { "/admin/ci/variables/#{variable.key}" } + + it_behaves_like 'GET request permissions for admin mode' it 'returns instance-level variable details for admins', :aggregate_failures do - get api("/admin/ci/variables/#{variable.key}", admin) + get api(path, admin, admin_mode: true) - expect(response).to have_gitlab_http_status(:ok) expect(json_response['value']).to eq(variable.value) expect(json_response['protected']).to eq(variable.protected?) expect(json_response['variable_type']).to eq(variable.variable_type) end it 'responds with 404 Not Found if requesting non-existing variable' do - get api('/admin/ci/variables/non_existing_variable', admin) + get api('/admin/ci/variables/non_existing_variable', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end - it 'does not return instance-level variable details for regular users' do - get api("/admin/ci/variables/#{variable.key}", user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - it 'does not return instance-level variable details for unauthorized users' do - get api("/admin/ci/variables/#{variable.key}") + get api(path, admin_mode: true) expect(response).to have_gitlab_http_status(:unauthorized) end end describe 'POST /admin/ci/variables' do - context 'authorized user with proper permissions' do - let!(:variable) { create(:ci_instance_variable) } + it_behaves_like 'POST request permissions for admin mode' do + let(:params) { { key: 'KEY', value: 'VALUE' } } + end + context 'authorized user with proper permissions' do it 'creates variable for admins', :aggregate_failures do expect do - post api('/admin/ci/variables', admin), + post api(path, admin, admin_mode: true), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', @@ -76,7 +68,6 @@ RSpec.describe ::API::Admin::Ci::Variables do } end.to change { ::Ci::InstanceVariable.count }.by(1) - expect(response).to have_gitlab_http_status(:created) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('PROTECTED_VALUE_2') expect(json_response['protected']).to be_truthy @@ -90,13 +81,13 @@ RSpec.describe ::API::Admin::Ci::Variables do expect(::API::API::LOGGER).to receive(:info).with(include(params: include(masked_params))) - post api("/admin/ci/variables", user), + post api(path, user, admin_mode: true), params: { key: 'VAR_KEY', value: 'SENSITIVE', protected: true, masked: true } end it 'creates variable with optional attributes', :aggregate_failures do expect do - post api('/admin/ci/variables', admin), + post api(path, admin, admin_mode: true), params: { variable_type: 'file', key: 'TEST_VARIABLE_2', @@ -104,7 +95,6 @@ RSpec.describe ::API::Admin::Ci::Variables do } end.to change { ::Ci::InstanceVariable.count }.by(1) - expect(response).to have_gitlab_http_status(:created) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_falsey @@ -115,20 +105,20 @@ RSpec.describe ::API::Admin::Ci::Variables do it 'does not allow to duplicate variable key' do expect do - post api('/admin/ci/variables', admin), + post api(path, admin, admin_mode: true), params: { key: variable.key, value: 'VALUE_2' } end.not_to change { ::Ci::InstanceVariable.count } expect(response).to have_gitlab_http_status(:bad_request) end - it 'does not allow values above 10,000 characters' do + it 'does not allow values above 10,000 characters', :aggregate_failures do too_long_message = <<~MESSAGE.strip The value of the provided variable exceeds the 10000 character limit MESSAGE expect do - post api('/admin/ci/variables', admin), + post api(path, admin, admin_mode: true), params: { key: 'too_long', value: SecureRandom.hex(10_001) } end.not_to change { ::Ci::InstanceVariable.count } @@ -138,17 +128,9 @@ RSpec.describe ::API::Admin::Ci::Variables do end end - context 'authorized user with invalid permissions' do - it 'does not create variable' do - post api('/admin/ci/variables', user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - context 'unauthorized user' do it 'does not create variable' do - post api('/admin/ci/variables') + post api(path, admin_mode: true) expect(response).to have_gitlab_http_status(:unauthorized) end @@ -156,20 +138,23 @@ RSpec.describe ::API::Admin::Ci::Variables do end describe 'PUT /admin/ci/variables/:key' do - let!(:variable) { create(:ci_instance_variable) } + let_it_be(:path) { "/admin/ci/variables/#{variable.key}" } + let_it_be(:params) do + { + variable_type: 'file', + value: 'VALUE_1_UP', + protected: true, + masked: true, + raw: true + } + end + + it_behaves_like 'PUT request permissions for admin mode' context 'authorized user with proper permissions' do it 'updates variable data', :aggregate_failures do - put api("/admin/ci/variables/#{variable.key}", admin), - params: { - variable_type: 'file', - value: 'VALUE_1_UP', - protected: true, - masked: true, - raw: true - } - - expect(response).to have_gitlab_http_status(:ok) + put api(path, admin, admin_mode: true), params: params + expect(variable.reload.value).to eq('VALUE_1_UP') expect(variable.reload).to be_protected expect(json_response['variable_type']).to eq('file') @@ -182,28 +167,20 @@ RSpec.describe ::API::Admin::Ci::Variables do expect(::API::API::LOGGER).to receive(:info).with(include(params: include(masked_params))) - put api("/admin/ci/variables/#{variable.key}", admin), + put api(path, admin, admin_mode: true), params: { value: 'SENSITIVE', protected: true, masked: true } end it 'responds with 404 Not Found if requesting non-existing variable' do - put api('/admin/ci/variables/non_existing_variable', admin) + put api('/admin/ci/variables/non_existing_variable', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end end - context 'authorized user with invalid permissions' do - it 'does not update variable' do - put api("/admin/ci/variables/#{variable.key}", user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - context 'unauthorized user' do it 'does not update variable' do - put api("/admin/ci/variables/#{variable.key}") + put api(path, admin_mode: true) expect(response).to have_gitlab_http_status(:unauthorized) end @@ -211,35 +188,27 @@ RSpec.describe ::API::Admin::Ci::Variables do end describe 'DELETE /admin/ci/variables/:key' do - let!(:variable) { create(:ci_instance_variable) } + let_it_be(:path) { "/admin/ci/variables/#{variable.key}" } + + it_behaves_like 'DELETE request permissions for admin mode' context 'authorized user with proper permissions' do it 'deletes variable' do expect do - delete api("/admin/ci/variables/#{variable.key}", admin) - - expect(response).to have_gitlab_http_status(:no_content) + delete api(path, admin, admin_mode: true) end.to change { ::Ci::InstanceVariable.count }.by(-1) end it 'responds with 404 Not Found if requesting non-existing variable' do - delete api('/admin/ci/variables/non_existing_variable', admin) + delete api('/admin/ci/variables/non_existing_variable', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end end - context 'authorized user with invalid permissions' do - it 'does not delete variable' do - delete api("/admin/ci/variables/#{variable.key}", user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - context 'unauthorized user' do it 'does not delete variable' do - delete api("/admin/ci/variables/#{variable.key}") + delete api(path, admin_mode: true) expect(response).to have_gitlab_http_status(:unauthorized) end diff --git a/spec/requests/api/admin/instance_clusters_spec.rb b/spec/requests/api/admin/instance_clusters_spec.rb index 7b510f74fd4..0a72f404e89 100644 --- a/spec/requests/api/admin/instance_clusters_spec.rb +++ b/spec/requests/api/admin/instance_clusters_spec.rb @@ -5,7 +5,6 @@ require 'spec_helper' RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_management do include KubernetesHelpers - let_it_be(:regular_user) { create(:user) } let_it_be(:admin_user) { create(:admin) } let_it_be(:project) { create(:project) } let_it_be(:project_cluster) do @@ -17,35 +16,27 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man let(:project_cluster_id) { project_cluster.id } describe "GET /admin/clusters" do + let_it_be(:path) { "/admin/clusters" } let_it_be(:clusters) do create_list(:cluster, 3, :provided_by_gcp, :instance, :production_environment) end - include_examples ':certificate_based_clusters feature flag API responses' do - let(:subject) { get api("/admin/clusters", admin_user) } - end + it_behaves_like 'GET request permissions for admin mode' - context "when authenticated as a non-admin user" do - it 'returns 403' do - get api('/admin/clusters', regular_user) - expect(response).to have_gitlab_http_status(:forbidden) - end + include_examples ':certificate_based_clusters feature flag API responses' do + let(:subject) { get api(path, admin_user, admin_mode: true) } end context "when authenticated as admin" do before do - get api("/admin/clusters", admin_user) - end - - it 'returns 200' do - expect(response).to have_gitlab_http_status(:ok) + get api(path, admin_user, admin_mode: true) end it 'includes pagination headers' do expect(response).to include_pagination_headers end - it 'only returns the instance clusters' do + it 'only returns the instance clusters', :aggregate_failures do cluster_ids = json_response.map { |cluster| cluster['id'] } expect(cluster_ids).to match_array(clusters.pluck(:id)) expect(cluster_ids).not_to include(project_cluster_id) @@ -60,19 +51,23 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man let_it_be(:cluster) do create(:cluster, :instance, :provided_by_gcp, :with_domain, - platform_kubernetes: platform_kubernetes, - user: admin_user) + { platform_kubernetes: platform_kubernetes, + user: admin_user }) end let(:cluster_id) { cluster.id } + let(:path) { "/admin/clusters/#{cluster_id}" } + + it_behaves_like 'GET request permissions for admin mode' + include_examples ':certificate_based_clusters feature flag API responses' do - let(:subject) { get api("/admin/clusters/#{cluster_id}", admin_user) } + let(:subject) { get api(path, admin_user, admin_mode: true) } end context "when authenticated as admin" do before do - get api("/admin/clusters/#{cluster_id}", admin_user) + get api(path, admin_user, admin_mode: true) end context "when no cluster associated to the ID" do @@ -84,15 +79,11 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man end context "when cluster with cluster_id exists" do - it 'returns 200' do - expect(response).to have_gitlab_http_status(:ok) - end - it 'returns the cluster with cluster_id' do expect(json_response['id']).to eq(cluster.id) end - it 'returns the cluster information' do + it 'returns the cluster information', :aggregate_failures do expect(json_response['provider_type']).to eq('gcp') expect(json_response['platform_type']).to eq('kubernetes') expect(json_response['environment_scope']).to eq('*') @@ -102,21 +93,21 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man expect(json_response['managed']).to be_truthy end - it 'returns kubernetes platform information' do + it 'returns kubernetes platform information', :aggregate_failures do platform = json_response['platform_kubernetes'] expect(platform['api_url']).to eq('https://kubernetes.example.com') expect(platform['ca_cert']).to be_present end - it 'returns user information' do + it 'returns user information', :aggregate_failures do user = json_response['user'] expect(user['id']).to eq(admin_user.id) expect(user['username']).to eq(admin_user.username) end - it 'returns GCP provider information' do + it 'returns GCP provider information', :aggregate_failures do gcp_provider = json_response['provider_gcp'] expect(gcp_provider['cluster_id']).to eq(cluster.id) @@ -140,18 +131,11 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man context 'when trying to get a project cluster via the instance cluster endpoint' do it 'returns 404' do - get api("/admin/clusters/#{project_cluster_id}", admin_user) + get api("/admin/clusters/#{project_cluster_id}", admin_user, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end end end - - context "when authenticated as a non-admin user" do - it 'returns 403' do - get api("/admin/clusters/#{cluster_id}", regular_user) - expect(response).to have_gitlab_http_status(:forbidden) - end - end end end @@ -159,6 +143,7 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man let(:api_url) { 'https://example.com' } let(:authorization_type) { 'rbac' } let(:clusterable) { Clusters::Instance.new } + let_it_be(:path) { '/admin/clusters/add' } let(:platform_kubernetes_attributes) do { @@ -196,20 +181,20 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man } end + it_behaves_like 'POST request permissions for admin mode' do + let(:params) { cluster_params } + end + include_examples ':certificate_based_clusters feature flag API responses' do - let(:subject) { post api('/admin/clusters/add', admin_user), params: cluster_params } + let(:subject) { post api(path, admin_user, admin_mode: true), params: cluster_params } end context 'authorized user' do before do - post api('/admin/clusters/add', admin_user), params: cluster_params + post api(path, admin_user, admin_mode: true), params: cluster_params end context 'with valid params' do - it 'responds with 201' do - expect(response).to have_gitlab_http_status(:created) - end - it 'creates a new Clusters::Cluster', :aggregate_failures do cluster_result = Clusters::Cluster.find(json_response["id"]) platform_kubernetes = cluster_result.platform @@ -271,7 +256,7 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man context 'when an instance cluster already exists' do it 'allows user to add multiple clusters' do - post api('/admin/clusters/add', admin_user), params: multiple_cluster_params + post api(path, admin_user, admin_mode: true), params: multiple_cluster_params expect(Clusters::Instance.new.clusters.count).to eq(2) end @@ -280,8 +265,8 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man context 'with invalid params' do context 'when missing a required parameter' do - it 'responds with 400' do - post api('/admin/clusters/add', admin_user), params: invalid_cluster_params + it 'responds with 400', :aggregate_failures do + post api(path, admin_user, admin_mode: true), params: invalid_cluster_params expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eql('name is missing') end @@ -300,14 +285,6 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man end end end - - context 'non-authorized user' do - it 'responds with 403' do - post api('/admin/clusters/add', regular_user), params: cluster_params - - expect(response).to have_gitlab_http_status(:forbidden) - end - end end describe 'PUT /admin/clusters/:cluster_id' do @@ -329,23 +306,25 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man create(:cluster, :instance, :provided_by_gcp, domain: 'old-domain.com') end + let(:path) { "/admin/clusters/#{cluster.id}" } + + it_behaves_like 'PUT request permissions for admin mode' do + let(:params) { update_params } + end + include_examples ':certificate_based_clusters feature flag API responses' do - let(:subject) { put api("/admin/clusters/#{cluster.id}", admin_user), params: update_params } + let(:subject) { put api(path, admin_user, admin_mode: true), params: update_params } end context 'authorized user' do before do - put api("/admin/clusters/#{cluster.id}", admin_user), params: update_params + put api(path, admin_user, admin_mode: true), params: update_params cluster.reload end context 'with valid params' do - it 'responds with 200' do - expect(response).to have_gitlab_http_status(:ok) - end - - it 'updates cluster attributes' do + it 'updates cluster attributes', :aggregate_failures do expect(cluster.domain).to eq('new-domain.com') expect(cluster.managed).to be_falsy expect(cluster.enabled).to be_falsy @@ -359,7 +338,7 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man expect(response).to have_gitlab_http_status(:bad_request) end - it 'does not update cluster attributes' do + it 'does not update cluster attributes', :aggregate_failures do expect(cluster.domain).to eq('old-domain.com') expect(cluster.managed).to be_truthy expect(cluster.enabled).to be_truthy @@ -422,7 +401,7 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man expect(response).to have_gitlab_http_status(:ok) end - it 'updates platform kubernetes attributes' do + it 'updates platform kubernetes attributes', :aggregate_failures do platform_kubernetes = cluster.platform_kubernetes expect(cluster.name).to eq('new-name') @@ -435,26 +414,18 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man let(:cluster_id) { 1337 } it 'returns 404' do - put api("/admin/clusters/#{cluster_id}", admin_user), params: update_params + put api("/admin/clusters/#{cluster_id}", admin_user, admin_mode: true), params: update_params expect(response).to have_gitlab_http_status(:not_found) end end context 'when trying to update a project cluster via the instance cluster endpoint' do it 'returns 404' do - put api("/admin/clusters/#{project_cluster_id}", admin_user), params: update_params + put api("/admin/clusters/#{project_cluster_id}", admin_user, admin_mode: true), params: update_params expect(response).to have_gitlab_http_status(:not_found) end end end - - context 'non-authorized user' do - it 'responds with 403' do - put api("/admin/clusters/#{cluster.id}", regular_user), params: update_params - - expect(response).to have_gitlab_http_status(:forbidden) - end - end end describe 'DELETE /admin/clusters/:cluster_id' do @@ -464,17 +435,17 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man create(:cluster, :instance, :provided_by_gcp) end + let_it_be(:path) { "/admin/clusters/#{cluster.id}" } + + it_behaves_like 'DELETE request permissions for admin mode' + include_examples ':certificate_based_clusters feature flag API responses' do - let(:subject) { delete api("/admin/clusters/#{cluster.id}", admin_user), params: cluster_params } + let(:subject) { delete api(path, admin_user, admin_mode: true), params: cluster_params } end context 'authorized user' do before do - delete api("/admin/clusters/#{cluster.id}", admin_user), params: cluster_params - end - - it 'responds with 204' do - expect(response).to have_gitlab_http_status(:no_content) + delete api(path, admin_user, admin_mode: true), params: cluster_params end it 'deletes the cluster' do @@ -485,25 +456,17 @@ RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_man let(:cluster_id) { 1337 } it 'returns 404' do - delete api("/admin/clusters/#{cluster_id}", admin_user) + delete api(path, admin_user, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end end context 'when trying to update a project cluster via the instance cluster endpoint' do it 'returns 404' do - delete api("/admin/clusters/#{project_cluster_id}", admin_user) + delete api("/admin/clusters/#{project_cluster_id}", admin_user, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end end end - - context 'non-authorized user' do - it 'responds with 403' do - delete api("/admin/clusters/#{cluster.id}", regular_user), params: cluster_params - - expect(response).to have_gitlab_http_status(:forbidden) - end - end end end diff --git a/spec/requests/api/admin/plan_limits_spec.rb b/spec/requests/api/admin/plan_limits_spec.rb index 2de7a66d803..dffe062c031 100644 --- a/spec/requests/api/admin/plan_limits_spec.rb +++ b/spec/requests/api/admin/plan_limits_spec.rb @@ -2,26 +2,19 @@ require 'spec_helper' -RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owned do - let_it_be(:user) { create(:user) } +RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :shared do let_it_be(:admin) { create(:admin) } let_it_be(:plan) { create(:plan, name: 'default') } + let_it_be(:path) { '/application/plan_limits' } describe 'GET /application/plan_limits' do - context 'as a non-admin user' do - it 'returns 403' do - get api('/application/plan_limits', user) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end + it_behaves_like 'GET request permissions for admin mode' context 'as an admin user' do context 'no params' do - it 'returns plan limits' do - get api('/application/plan_limits', admin) + it 'returns plan limits', :aggregate_failures do + get api(path, admin, admin_mode: true) - expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Hash expect(json_response['ci_pipeline_size']).to eq(Plan.default.actual_limits.ci_pipeline_size) expect(json_response['ci_active_jobs']).to eq(Plan.default.actual_limits.ci_active_jobs) @@ -49,8 +42,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owne @params = { plan_name: 'default' } end - it 'returns plan limits' do - get api('/application/plan_limits', admin), params: @params + it 'returns plan limits', :aggregate_failures do + get api(path, admin, admin_mode: true), params: @params expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Hash @@ -80,8 +73,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owne @params = { plan_name: 'my-plan' } end - it 'returns validation error' do - get api('/application/plan_limits', admin), params: @params + it 'returns validation error', :aggregate_failures do + get api(path, admin, admin_mode: true), params: @params expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq('plan_name does not have a valid value') @@ -91,18 +84,14 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owne end describe 'PUT /application/plan_limits' do - context 'as a non-admin user' do - it 'returns 403' do - put api('/application/plan_limits', user), params: { plan_name: 'default' } - - expect(response).to have_gitlab_http_status(:forbidden) - end + it_behaves_like 'PUT request permissions for admin mode' do + let(:params) { { 'plan_name': 'default' } } end context 'as an admin user' do context 'correct params' do - it 'updates multiple plan limits' do - put api('/application/plan_limits', admin), params: { + it 'updates multiple plan limits', :aggregate_failures do + put api(path, admin, admin_mode: true), params: { 'plan_name': 'default', 'ci_pipeline_size': 101, 'ci_active_jobs': 102, @@ -124,7 +113,6 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owne 'pipeline_hierarchy_size': 250 } - expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Hash expect(json_response['ci_pipeline_size']).to eq(101) expect(json_response['ci_active_jobs']).to eq(102) @@ -146,8 +134,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owne expect(json_response['pipeline_hierarchy_size']).to eq(250) end - it 'updates single plan limits' do - put api('/application/plan_limits', admin), params: { + it 'updates single plan limits', :aggregate_failures do + put api(path, admin, admin_mode: true), params: { 'plan_name': 'default', 'maven_max_file_size': 100 } @@ -159,8 +147,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owne end context 'empty params' do - it 'fails to update plan limits' do - put api('/application/plan_limits', admin), params: {} + it 'fails to update plan limits', :aggregate_failures do + put api(path, admin, admin_mode: true), params: {} expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to match('plan_name is missing') @@ -168,8 +156,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owne end context 'params with wrong type' do - it 'fails to update plan limits' do - put api('/application/plan_limits', admin), params: { + it 'fails to update plan limits', :aggregate_failures do + put api(path, admin, admin_mode: true), params: { 'plan_name': 'default', 'ci_pipeline_size': 'z', 'ci_active_jobs': 'y', @@ -216,8 +204,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owne end context 'missing plan_name in params' do - it 'fails to update plan limits' do - put api('/application/plan_limits', admin), params: { 'conan_max_file_size': 0 } + it 'fails to update plan limits', :aggregate_failures do + put api(path, admin, admin_mode: true), params: { 'conan_max_file_size': 0 } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to match('plan_name is missing') @@ -229,8 +217,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owne Plan.default.actual_limits.update!({ 'golang_max_file_size': 1000 }) end - it 'updates only declared plan limits' do - put api('/application/plan_limits', admin), params: { + it 'updates only declared plan limits', :aggregate_failures do + put api(path, admin, admin_mode: true), params: { 'plan_name': 'default', 'pypi_max_file_size': 200, 'golang_max_file_size': 999 diff --git a/spec/requests/api/admin/sidekiq_spec.rb b/spec/requests/api/admin/sidekiq_spec.rb index 0b456721d4f..8bcd7884fd2 100644 --- a/spec/requests/api/admin/sidekiq_spec.rb +++ b/spec/requests/api/admin/sidekiq_spec.rb @@ -2,18 +2,10 @@ require 'spec_helper' -RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues, feature_category: :not_owned do +RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues, feature_category: :shared do let_it_be(:admin) { create(:admin) } describe 'DELETE /admin/sidekiq/queues/:queue_name' do - context 'when the user is not an admin' do - it 'returns a 403' do - delete api("/admin/sidekiq/queues/authorized_projects?user=#{admin.username}", create(:user)) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - context 'when the user is an admin' do around do |example| Sidekiq::Queue.new('authorized_projects').clear @@ -31,14 +23,19 @@ RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues, feature_category end context 'valid request' do - it 'returns info about the deleted jobs' do + before do add_job(admin, [1]) add_job(admin, [2]) add_job(create(:user), [3]) + end + + let_it_be(:path) { "/admin/sidekiq/queues/authorized_projects?user=#{admin.username}&worker_class=AuthorizedProjectsWorker" } - delete api("/admin/sidekiq/queues/authorized_projects?user=#{admin.username}&worker_class=AuthorizedProjectsWorker", admin) + it_behaves_like 'DELETE request permissions for admin mode', success_status_code: :ok + + it 'returns info about the deleted jobs' do + delete api(path, admin, admin_mode: true) - expect(response).to have_gitlab_http_status(:ok) expect(json_response).to eq('completed' => true, 'deleted_jobs' => 2, 'queue_size' => 1) @@ -47,7 +44,7 @@ RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues, feature_category context 'when no required params are provided' do it 'returns a 400' do - delete api("/admin/sidekiq/queues/authorized_projects?user_2=#{admin.username}", admin) + delete api("/admin/sidekiq/queues/authorized_projects?user_2=#{admin.username}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -55,7 +52,7 @@ RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues, feature_category context 'when the queue does not exist' do it 'returns a 404' do - delete api("/admin/sidekiq/queues/authorized_projects_2?user=#{admin.username}", admin) + delete api("/admin/sidekiq/queues/authorized_projects_2?user=#{admin.username}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end diff --git a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb index 21f3691c20b..7268fa2c90b 100644 --- a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb +++ b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::APIGuard::AdminModeMiddleware, :request_store, feature_category: :not_owned do +RSpec.describe API::APIGuard::AdminModeMiddleware, :request_store, feature_category: :shared do let(:user) { create(:admin) } it 'is loaded' do diff --git a/spec/requests/api/api_guard/response_coercer_middleware_spec.rb b/spec/requests/api/api_guard/response_coercer_middleware_spec.rb index 77498c2e2b3..4a993d0b255 100644 --- a/spec/requests/api/api_guard/response_coercer_middleware_spec.rb +++ b/spec/requests/api/api_guard/response_coercer_middleware_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::APIGuard::ResponseCoercerMiddleware, feature_category: :not_owned do +RSpec.describe API::APIGuard::ResponseCoercerMiddleware, feature_category: :shared do using RSpec::Parameterized::TableSyntax it 'is loaded' do diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb index 35851fff6c8..d5ad0779bd9 100644 --- a/spec/requests/api/api_spec.rb +++ b/spec/requests/api/api_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::API, feature_category: :authentication_and_authorization do +RSpec.describe API::API, feature_category: :system_access do include GroupAPIHelpers describe 'Record user last activity in after hook' do diff --git a/spec/requests/api/appearance_spec.rb b/spec/requests/api/appearance_spec.rb index c08ecae28e8..3550e51d585 100644 --- a/spec/requests/api/appearance_spec.rb +++ b/spec/requests/api/appearance_spec.rb @@ -36,7 +36,9 @@ RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do end describe "PUT /application/appearance" do - it_behaves_like 'PUT request permissions for admin mode', { title: "Test" } + it_behaves_like 'PUT request permissions for admin mode' do + let(:params) { { title: "Test" } } + end context 'as an admin user' do context "instance basics" do diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb index b81cdcfea8e..5b07bded82c 100644 --- a/spec/requests/api/applications_spec.rb +++ b/spec/requests/api/applications_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Applications, :api, feature_category: :authentication_and_authorization do +RSpec.describe API::Applications, :api, feature_category: :system_access do let_it_be(:admin) { create(:admin) } let_it_be(:user) { create(:user) } let_it_be(:scopes) { 'api' } @@ -10,7 +10,9 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au let!(:application) { create(:application, name: 'another_application', owner: nil, redirect_uri: 'http://other_application.url', scopes: scopes) } describe 'POST /applications' do - it_behaves_like 'POST request permissions for admin mode', { name: 'application_name', redirect_uri: 'http://application.url', scopes: 'api' } + it_behaves_like 'POST request permissions for admin mode' do + let(:params) { { name: 'application_name', redirect_uri: 'http://application.url', scopes: 'api' } } + end context 'authenticated and authorized user' do it 'creates and returns an OAuth application' do @@ -22,7 +24,7 @@ RSpec.describe API::Applications, :api, feature_category: :authentication_and_au expect(json_response).to be_a Hash expect(json_response['application_id']).to eq application.uid - expect(json_response['secret']).to eq application.secret + expect(application.secret_matches?(json_response['secret'])).to eq(true) expect(json_response['callback_url']).to eq application.redirect_uri expect(json_response['confidential']).to eq application.confidential expect(application.scopes.to_s).to eq('api') diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb index fcef5b6ca78..0a77b6e228e 100644 --- a/spec/requests/api/avatar_spec.rb +++ b/spec/requests/api/avatar_spec.rb @@ -19,6 +19,7 @@ RSpec.describe API::Avatar, feature_category: :user_profile do expect(response).to have_gitlab_http_status(:ok) expect(json_response['avatar_url']).to eql("#{::Settings.gitlab.base_url}#{user.avatar.local_url}") + is_expected.to have_request_urgency(:medium) end end diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 87dc06b7d15..22c67a253e3 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::AwardEmoji, feature_category: :not_owned do +RSpec.describe API::AwardEmoji, feature_category: :shared do let_it_be_with_reload(:project) { create(:project, :private) } let_it_be(:user) { create(:user) } let_it_be(:issue) { create(:issue, project: project) } diff --git a/spec/requests/api/ci/job_artifacts_spec.rb b/spec/requests/api/ci/job_artifacts_spec.rb index ee390773f29..7cea744cdb9 100644 --- a/spec/requests/api/ci/job_artifacts_spec.rb +++ b/spec/requests/api/ci/job_artifacts_spec.rb @@ -190,7 +190,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do end context 'when project is public with artifacts that are non public' do - let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) } + let(:job) { create(:ci_build, :artifacts, :with_private_artifacts_config, pipeline: pipeline) } it 'rejects access to artifacts' do project.update_column(:visibility_level, @@ -439,7 +439,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do context 'when public project guest and artifacts are non public' do let(:api_user) { guest } - let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) } + let(:job) { create(:ci_build, :artifacts, :with_private_artifacts_config, pipeline: pipeline) } before do project.update_column(:visibility_level, @@ -644,7 +644,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do end context 'when project is public with non public artifacts' do - let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline, user: api_user) } + let(:job) { create(:ci_build, :artifacts, :with_private_artifacts_config, pipeline: pipeline, user: api_user) } let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC } let(:public_builds) { true } diff --git a/spec/requests/api/ci/pipeline_schedules_spec.rb b/spec/requests/api/ci/pipeline_schedules_spec.rb index 2a2c5f65aee..d760e4ddf28 100644 --- a/spec/requests/api/ci/pipeline_schedules_spec.rb +++ b/spec/requests/api/ci/pipeline_schedules_spec.rb @@ -473,12 +473,12 @@ RSpec.describe API::Ci::PipelineSchedules, feature_category: :continuous_integra end context 'as the existing owner of the schedule' do - it 'rejects the request and leaves the schedule unchanged' do + it 'accepts the request and leaves the schedule unchanged' do expect do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer) end.not_to change { pipeline_schedule.reload.owner } - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:success) end end end diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb index 6d69da85449..4e81a052ecf 100644 --- a/spec/requests/api/ci/pipelines_spec.rb +++ b/spec/requests/api/ci/pipelines_spec.rb @@ -25,7 +25,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do it_behaves_like 'pipelines visibility table' context 'authorized user' do - it 'returns project pipelines' do + it 'returns project pipelines', :aggregate_failures do get api("/projects/#{project.id}/pipelines", user) expect(response).to have_gitlab_http_status(:ok) @@ -52,7 +52,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do create(:ci_pipeline, project: project, status: target) end - it 'returns matched pipelines' do + it 'returns matched pipelines', :aggregate_failures do get api("/projects/#{project.id}/pipelines", user), params: { scope: target } expect(response).to have_gitlab_http_status(:ok) @@ -307,7 +307,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'unauthorized user' do - it 'does not return project pipelines' do + it 'does not return project pipelines', :aggregate_failures do get api("/projects/#{project.id}/pipelines", non_member) expect(response).to have_gitlab_http_status(:not_found) @@ -335,13 +335,13 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'authorized user' do - it 'returns pipeline jobs' do + it 'returns pipeline jobs', :aggregate_failures do expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_an Array end - it 'returns correct values' do + it 'returns correct values', :aggregate_failures do expect(json_response).not_to be_empty expect(json_response.first['commit']['id']).to eq project.commit.id expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) @@ -354,7 +354,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do let(:api_endpoint) { "/projects/#{project.id}/pipelines/#{pipeline.id}/jobs" } end - it 'returns pipeline data' do + it 'returns pipeline data', :aggregate_failures do json_job = json_response.first expect(json_job['pipeline']).not_to be_empty @@ -368,7 +368,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'filter jobs with one scope element' do let(:query) { { 'scope' => 'pending' } } - it do + it :aggregate_failures do expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Array @@ -382,7 +382,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'when filtering to only running jobs' do let(:query) { { 'scope' => 'running' } } - it do + it :aggregate_failures do expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Array @@ -402,7 +402,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'filter jobs with array of scope elements' do let(:query) { { scope: %w(pending running) } } - it do + it :aggregate_failures do expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Array end @@ -442,7 +442,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do let_it_be(:successor) { create(:ci_build, :success, name: 'build', pipeline: pipeline) } - it 'does not return retried jobs by default' do + it 'does not return retried jobs by default', :aggregate_failures do expect(json_response).to be_an Array expect(json_response.length).to eq(1) end @@ -450,7 +450,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'when include_retried is false' do let(:query) { { include_retried: false } } - it 'does not return retried jobs' do + it 'does not return retried jobs', :aggregate_failures do expect(json_response).to be_an Array expect(json_response.length).to eq(1) end @@ -459,7 +459,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'when include_retried is true' do let(:query) { { include_retried: true } } - it 'returns retried jobs' do + it 'returns retried jobs', :aggregate_failures do expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response[0]['name']).to eq(json_response[1]['name']) @@ -469,7 +469,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'no pipeline is found' do - it 'does not return jobs' do + it 'does not return jobs', :aggregate_failures do get api("/projects/#{project2.id}/pipelines/#{pipeline.id}/jobs", user) expect(json_response['message']).to eq '404 Project Not Found' @@ -481,7 +481,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'when user is not logged in' do let(:api_user) { nil } - it 'does not return jobs' do + it 'does not return jobs', :aggregate_failures do expect(json_response['message']).to eq '404 Project Not Found' expect(response).to have_gitlab_http_status(:not_found) end @@ -523,13 +523,13 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'authorized user' do - it 'returns pipeline bridges' do + it 'returns pipeline bridges', :aggregate_failures do expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_an Array end - it 'returns correct values' do + it 'returns correct values', :aggregate_failures do expect(json_response).not_to be_empty expect(json_response.first['commit']['id']).to eq project.commit.id expect(json_response.first['id']).to eq bridge.id @@ -537,7 +537,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do expect(json_response.first['stage']).to eq bridge.stage end - it 'returns pipeline data' do + it 'returns pipeline data', :aggregate_failures do json_bridge = json_response.first expect(json_bridge['pipeline']).not_to be_empty @@ -548,7 +548,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do expect(json_bridge['pipeline']['status']).to eq bridge.pipeline.status end - it 'returns downstream pipeline data' do + it 'returns downstream pipeline data', :aggregate_failures do json_bridge = json_response.first expect(json_bridge['downstream_pipeline']).not_to be_empty @@ -568,7 +568,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'with one scope element' do let(:query) { { 'scope' => 'pending' } } - it :skip_before_request do + it :skip_before_request, :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query expect(response).to have_gitlab_http_status(:ok) @@ -581,7 +581,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'with array of scope elements' do let(:query) { { scope: %w(pending running) } } - it :skip_before_request do + it :skip_before_request, :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query expect(response).to have_gitlab_http_status(:ok) @@ -635,7 +635,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'no pipeline is found' do - it 'does not return bridges' do + it 'does not return bridges', :aggregate_failures do get api("/projects/#{project2.id}/pipelines/#{pipeline.id}/bridges", user) expect(json_response['message']).to eq '404 Project Not Found' @@ -647,7 +647,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'when user is not logged in' do let(:api_user) { nil } - it 'does not return bridges' do + it 'does not return bridges', :aggregate_failures do expect(json_response['message']).to eq '404 Project Not Found' expect(response).to have_gitlab_http_status(:not_found) end @@ -704,7 +704,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do stub_ci_pipeline_to_return_yaml_file end - it 'creates and returns a new pipeline' do + it 'creates and returns a new pipeline', :aggregate_failures do expect do post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch } end.to change { project.ci_pipelines.count }.by(1) @@ -717,7 +717,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'variables given' do let(:variables) { [{ 'variable_type' => 'file', 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] } - it 'creates and returns a new pipeline using the given variables' do + it 'creates and returns a new pipeline using the given variables', :aggregate_failures do expect do post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables } end.to change { project.ci_pipelines.count }.by(1) @@ -738,7 +738,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do stub_ci_pipeline_yaml_file(config) end - it 'creates and returns a new pipeline using the given variables' do + it 'creates and returns a new pipeline using the given variables', :aggregate_failures do expect do post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables } end.to change { project.ci_pipelines.count }.by(1) @@ -763,7 +763,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end end - it 'fails when using an invalid ref' do + it 'fails when using an invalid ref', :aggregate_failures do post api("/projects/#{project.id}/pipeline", user), params: { ref: 'invalid_ref' } expect(response).to have_gitlab_http_status(:bad_request) @@ -778,7 +778,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do project.update!(auto_devops_attributes: { enabled: false }) end - it 'fails to create pipeline' do + it 'fails to create pipeline', :aggregate_failures do post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch } expect(response).to have_gitlab_http_status(:bad_request) @@ -790,7 +790,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'unauthorized user' do - it 'does not create pipeline' do + it 'does not create pipeline', :aggregate_failures do post api("/projects/#{project.id}/pipeline", non_member), params: { ref: project.default_branch } expect(response).to have_gitlab_http_status(:not_found) @@ -811,21 +811,21 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'authorized user' do - it 'exposes known attributes' do + it 'exposes known attributes', :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('public_api/v4/pipeline/detail') end - it 'returns project pipeline' do + it 'returns project pipeline', :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) expect(response).to have_gitlab_http_status(:ok) expect(json_response['sha']).to match(/\A\h{40}\z/) end - it 'returns 404 when it does not exist' do + it 'returns 404 when it does not exist', :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{non_existing_record_id}", user) expect(response).to have_gitlab_http_status(:not_found) @@ -847,7 +847,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'unauthorized user' do - it 'does not return a project pipeline' do + it 'does not return a project pipeline', :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) expect(response).to have_gitlab_http_status(:not_found) @@ -863,7 +863,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do create(:ci_pipeline, source: dangling_source, project: project) end - it 'returns the specified pipeline' do + it 'returns the specified pipeline', :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{dangling_pipeline.id}", user) expect(response).to have_gitlab_http_status(:ok) @@ -887,7 +887,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'default repository branch' do - it 'gets the latest pipleine' do + it 'gets the latest pipleine', :aggregate_failures do get api("/projects/#{project.id}/pipelines/latest", user) expect(response).to have_gitlab_http_status(:ok) @@ -898,7 +898,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'ref parameter' do - it 'gets the latest pipleine' do + it 'gets the latest pipleine', :aggregate_failures do get api("/projects/#{project.id}/pipelines/latest", user), params: { ref: second_branch.name } expect(response).to have_gitlab_http_status(:ok) @@ -910,7 +910,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'unauthorized user' do - it 'does not return a project pipeline' do + it 'does not return a project pipeline', :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) expect(response).to have_gitlab_http_status(:not_found) @@ -926,7 +926,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do let(:api_user) { user } context 'user is a mantainer' do - it 'returns pipeline variables empty' do + it 'returns pipeline variables empty', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) @@ -936,7 +936,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'with variables' do let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') } - it 'returns pipeline variables' do + it 'returns pipeline variables', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) @@ -962,7 +962,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do let(:api_user) { pipeline_owner_user } let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') } - it 'returns pipeline variables' do + it 'returns pipeline variables', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) @@ -987,7 +987,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'user is not a project member' do - it 'does not return pipeline variables' do + it 'does not return pipeline variables', :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", non_member) expect(response).to have_gitlab_http_status(:not_found) @@ -1000,14 +1000,14 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'authorized user' do let(:owner) { project.first_owner } - it 'destroys the pipeline' do + it 'destroys the pipeline', :aggregate_failures do delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner) expect(response).to have_gitlab_http_status(:no_content) expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound) end - it 'returns 404 when it does not exist' do + it 'returns 404 when it does not exist', :aggregate_failures do delete api("/projects/#{project.id}/pipelines/#{non_existing_record_id}", owner) expect(response).to have_gitlab_http_status(:not_found) @@ -1021,7 +1021,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'when the pipeline has jobs' do let_it_be(:build) { create(:ci_build, project: project, pipeline: pipeline) } - it 'destroys associated jobs' do + it 'destroys associated jobs', :aggregate_failures do delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner) expect(response).to have_gitlab_http_status(:no_content) @@ -1044,7 +1044,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'unauthorized user' do context 'when user is not member' do - it 'returns a 404' do + it 'returns a 404', :aggregate_failures do delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) expect(response).to have_gitlab_http_status(:not_found) @@ -1059,7 +1059,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do project.add_developer(developer) end - it 'returns a 403' do + it 'returns a 403', :aggregate_failures do delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", developer) expect(response).to have_gitlab_http_status(:forbidden) @@ -1078,7 +1078,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do let_it_be(:build) { create(:ci_build, :failed, pipeline: pipeline) } - it 'retries failed builds' do + it 'retries failed builds', :aggregate_failures do expect do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", user) end.to change { pipeline.builds.count }.from(1).to(2) @@ -1089,7 +1089,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'unauthorized user' do - it 'does not return a project pipeline' do + it 'does not return a project pipeline', :aggregate_failures do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member) expect(response).to have_gitlab_http_status(:not_found) @@ -1106,7 +1106,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end end - it 'returns error' do + it 'returns error', :aggregate_failures do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", user) expect(response).to have_gitlab_http_status(:forbidden) @@ -1124,7 +1124,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do let_it_be(:build) { create(:ci_build, :running, pipeline: pipeline) } - context 'authorized user' do + context 'authorized user', :aggregate_failures do it 'retries failed builds', :sidekiq_might_not_need_inline do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", user) @@ -1140,7 +1140,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do project.add_reporter(reporter) end - it 'rejects the action' do + it 'rejects the action', :aggregate_failures do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", reporter) expect(response).to have_gitlab_http_status(:forbidden) @@ -1156,7 +1156,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do let(:pipeline) { create(:ci_pipeline, project: project) } context 'when pipeline does not have a test report' do - it 'returns an empty test report' do + it 'returns an empty test report', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) @@ -1167,7 +1167,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'when pipeline has a test report' do let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project) } - it 'returns the test report' do + it 'returns the test report', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) @@ -1180,7 +1180,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do create(:ci_build, :broken_test_reports, name: 'rspec', pipeline: pipeline) end - it 'returns a suite_error' do + it 'returns a suite_error', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) @@ -1190,7 +1190,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'unauthorized user' do - it 'does not return project pipelines' do + it 'does not return project pipelines', :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report", non_member) expect(response).to have_gitlab_http_status(:not_found) @@ -1208,7 +1208,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do let(:pipeline) { create(:ci_pipeline, project: project) } context 'when pipeline does not have a test report summary' do - it 'returns an empty test report summary' do + it 'returns an empty test report summary', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) @@ -1219,7 +1219,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do context 'when pipeline has a test report summary' do let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project) } - it 'returns the test report summary' do + it 'returns the test report summary', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) @@ -1229,7 +1229,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do end context 'unauthorized user' do - it 'does not return project pipelines' do + it 'does not return project pipelines', :aggregate_failures do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report_summary", non_member) expect(response).to have_gitlab_http_status(:not_found) diff --git a/spec/requests/api/ci/runner/jobs_put_spec.rb b/spec/requests/api/ci/runner/jobs_put_spec.rb index ef3b38e3fc4..bf28b25e0a6 100644 --- a/spec/requests/api/ci/runner/jobs_put_spec.rb +++ b/spec/requests/api/ci/runner/jobs_put_spec.rb @@ -43,6 +43,17 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego .and change { runner_machine.reload.contacted_at } end + context 'when runner_machine_heartbeat is disabled' do + before do + stub_feature_flags(runner_machine_heartbeat: false) + end + + it 'does not load runner machine' do + queries = ActiveRecord::QueryRecorder.new { update_job(state: 'success') } + expect(queries.log).not_to include(/ci_runner_machines/) + end + end + context 'when status is given' do it 'marks job as succeeded' do update_job(state: 'success') diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb index 6e721d40560..28dbc4fd168 100644 --- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb +++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb @@ -831,19 +831,6 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego end end end - - context 'when the FF ci_hooks_pre_get_sources_script is disabled' do - before do - stub_feature_flags(ci_hooks_pre_get_sources_script: false) - end - - it 'does not return the pre_get_sources_script' do - request_job - - expect(response).to have_gitlab_http_status(:created) - expect(json_response).not_to have_key('hooks') - end - end end describe 'port support' do diff --git a/spec/requests/api/ci/runner/runners_verify_post_spec.rb b/spec/requests/api/ci/runner/runners_verify_post_spec.rb index a6a1ad947aa..1b7dfe7706c 100644 --- a/spec/requests/api/ci/runner/runners_verify_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_verify_post_spec.rb @@ -17,7 +17,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego end describe '/api/v4/runners' do - describe 'POST /api/v4/runners/verify' do + describe 'POST /api/v4/runners/verify', :freeze_time do let_it_be_with_reload(:runner) { create(:ci_runner, token_expires_at: 3.days.from_now) } let(:params) {} @@ -50,6 +50,30 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego stub_feature_flags(create_runner_machine: true) end + context 'with glrt-prefixed token' do + let_it_be(:registration_token) { 'glrt-abcdefg123456' } + let_it_be(:registration_type) { :authenticated_user } + let_it_be(:runner) do + create(:ci_runner, registration_type: registration_type, + token: registration_token, token_expires_at: 3.days.from_now) + end + + it 'verifies Runner credentials' do + verify + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq({ + 'id' => runner.id, + 'token' => runner.token, + 'token_expires_at' => runner.token_expires_at.iso8601(3) + }) + end + + it 'does not update contacted_at' do + expect { verify }.not_to change { runner.reload.contacted_at }.from(nil) + end + end + it 'verifies Runner credentials' do verify @@ -61,6 +85,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego }) end + it 'updates contacted_at' do + expect { verify }.to change { runner.reload.contacted_at }.from(nil).to(Time.current) + end + context 'with non-expiring runner token' do before do runner.update!(token_expires_at: nil) diff --git a/spec/requests/api/ci/runners_reset_registration_token_spec.rb b/spec/requests/api/ci/runners_reset_registration_token_spec.rb index 1110dbf5fbc..98edde93e95 100644 --- a/spec/requests/api/ci/runners_reset_registration_token_spec.rb +++ b/spec/requests/api/ci/runners_reset_registration_token_spec.rb @@ -3,10 +3,12 @@ require 'spec_helper' RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do - subject { post api("#{prefix}/runners/reset_registration_token", user) } + let_it_be(:admin_mode) { false } + + subject { post api("#{prefix}/runners/reset_registration_token", user, admin_mode: admin_mode) } shared_examples 'bad request' do |result| - it 'returns 400 error' do + it 'returns 400 error', :aggregate_failures do expect { subject }.not_to change { get_token } expect(response).to have_gitlab_http_status(:bad_request) @@ -15,7 +17,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end shared_examples 'unauthenticated' do - it 'returns 401 error' do + it 'returns 401 error', :aggregate_failures do expect { subject }.not_to change { get_token } expect(response).to have_gitlab_http_status(:unauthorized) @@ -23,7 +25,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end shared_examples 'unauthorized' do - it 'returns 403 error' do + it 'returns 403 error', :aggregate_failures do expect { subject }.not_to change { get_token } expect(response).to have_gitlab_http_status(:forbidden) @@ -31,7 +33,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end shared_examples 'not found' do |scope| - it 'returns 404 error' do + it 'returns 404 error', :aggregate_failures do expect { subject }.not_to change { get_token } expect(response).to have_gitlab_http_status(:not_found) @@ -58,7 +60,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end shared_context 'when authorized' do |scope| - it 'resets runner registration token' do + it 'resets runner registration token', :aggregate_failures do expect { subject }.to change { get_token } expect(response).to have_gitlab_http_status(:success) @@ -99,6 +101,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do include_context 'when authorized', 'instance' do let_it_be(:user) { create(:user, :admin) } + let_it_be(:admin_mode) { true } def get_token ApplicationSetting.current_without_cache.runners_registration_token diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb index ca051386265..ec9b5621c37 100644 --- a/spec/requests/api/ci/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -35,7 +35,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do describe 'GET /runners' do context 'authorized user' do - it 'returns response status and headers' do + it 'returns response status and headers', :aggregate_failures do get api('/runners', user) expect(response).to have_gitlab_http_status(:ok) @@ -53,7 +53,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do ] end - it 'filters runners by scope' do + it 'filters runners by scope', :aggregate_failures do create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project]) get api('/runners?scope=paused', user) @@ -112,7 +112,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(response).to have_gitlab_http_status(:bad_request) end - it 'filters runners by tag_list' do + it 'filters runners by tag_list', :aggregate_failures do create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2]) create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2']) @@ -137,14 +137,14 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context 'authorized user' do context 'with admin privileges' do it 'returns response status and headers' do - get api('/runners/all', admin) + get api('/runners/all', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers end it 'returns all runners' do - get api('/runners/all', admin) + get api('/runners/all', admin, admin_mode: true) expect(json_response).to match_array [ a_hash_including('description' => 'Project runner', 'is_shared' => false, 'active' => true, 'paused' => false, 'runner_type' => 'project_type'), @@ -155,8 +155,8 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do ] end - it 'filters runners by scope' do - get api('/runners/all?scope=shared', admin) + it 'filters runners by scope', :aggregate_failures do + get api('/runners/all?scope=shared', admin, admin_mode: true) shared = json_response.all? { |r| r['is_shared'] } expect(response).to have_gitlab_http_status(:ok) @@ -166,8 +166,8 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(shared).to be_truthy end - it 'filters runners by scope' do - get api('/runners/all?scope=specific', admin) + it 'filters runners by scope', :aggregate_failures do + get api('/runners/all?scope=specific', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -181,12 +181,12 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end it 'avoids filtering if scope is invalid' do - get api('/runners/all?scope=unknown', admin) + get api('/runners/all?scope=unknown', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end it 'filters runners by project type' do - get api('/runners/all?type=project_type', admin) + get api('/runners/all?type=project_type', admin, admin_mode: true) expect(json_response).to match_array [ a_hash_including('description' => 'Project runner'), @@ -195,7 +195,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end it 'filters runners by group type' do - get api('/runners/all?type=group_type', admin) + get api('/runners/all?type=group_type', admin, admin_mode: true) expect(json_response).to match_array [ a_hash_including('description' => 'Group runner A'), @@ -204,7 +204,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end it 'does not filter by invalid type' do - get api('/runners/all?type=bogus', admin) + get api('/runners/all?type=bogus', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -213,7 +213,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do let_it_be(:runner) { create(:ci_runner, :project, :inactive, description: 'Inactive project runner', projects: [project]) } it 'filters runners by status' do - get api('/runners/all?paused=true', admin) + get api('/runners/all?paused=true', admin, admin_mode: true) expect(json_response).to match_array [ a_hash_including('description' => 'Inactive project runner') @@ -221,7 +221,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end it 'filters runners by status' do - get api('/runners/all?status=paused', admin) + get api('/runners/all?status=paused', admin, admin_mode: true) expect(json_response).to match_array [ a_hash_including('description' => 'Inactive project runner') @@ -230,16 +230,16 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end it 'does not filter by invalid status' do - get api('/runners/all?status=bogus', admin) + get api('/runners/all?status=bogus', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end - it 'filters runners by tag_list' do + it 'filters runners by tag_list', :aggregate_failures do create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2]) create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2']) - get api('/runners/all?tag_list=tag1,tag2', admin) + get api('/runners/all?tag_list=tag1,tag2', admin, admin_mode: true) expect(json_response).to match_array [ a_hash_including('description' => 'Runner tagged with tag1 and tag2') @@ -268,7 +268,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do describe 'GET /runners/:id' do context 'admin user' do context 'when runner is shared' do - it "returns runner's details" do + it "returns runner's details", :aggregate_failures do get api("/runners/#{shared_runner.id}", admin) expect(response).to have_gitlab_http_status(:ok) @@ -284,39 +284,39 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context 'when unused runner is present' do let!(:unused_project_runner) { create(:ci_runner, :project, :without_projects) } - it 'deletes unused runner' do + it 'deletes unused runner', :aggregate_failures do expect do - delete api("/runners/#{unused_project_runner.id}", admin) + delete api("/runners/#{unused_project_runner.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:no_content) end.to change { ::Ci::Runner.project_type.count }.by(-1) end end - it "returns runner's details" do - get api("/runners/#{project_runner.id}", admin) + it "returns runner's details", :aggregate_failures do + get api("/runners/#{project_runner.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response['description']).to eq(project_runner.description) end it "returns the project's details for a project runner" do - get api("/runners/#{project_runner.id}", admin) + get api("/runners/#{project_runner.id}", admin, admin_mode: true) expect(json_response['projects'].first['id']).to eq(project.id) end end it 'returns 404 if runner does not exist' do - get api('/runners/0', admin) + get api('/runners/0', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end end context 'when the runner is a group runner' do - it "returns the runner's details" do - get api("/runners/#{group_runner_a.id}", admin) + it "returns the runner's details", :aggregate_failures do + get api("/runners/#{group_runner_a.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response['description']).to eq(group_runner_a.description) @@ -326,7 +326,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context "runner project's administrative user" do context 'when runner is not shared' do - it "returns runner's details" do + it "returns runner's details", :aggregate_failures do get api("/runners/#{project_runner.id}", user) expect(response).to have_gitlab_http_status(:ok) @@ -335,7 +335,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when runner is shared' do - it "returns runner's details" do + it "returns runner's details", :aggregate_failures do get api("/runners/#{shared_runner.id}", user) expect(response).to have_gitlab_http_status(:ok) @@ -373,7 +373,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(shared_runner.reload.description).to eq("#{description}_updated") end - it 'runner active state' do + it 'runner active state', :aggregate_failures do active = shared_runner.active update_runner(shared_runner.id, admin, active: !active) @@ -381,7 +381,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(shared_runner.reload.active).to eq(!active) end - it 'runner paused state' do + it 'runner paused state', :aggregate_failures do active = shared_runner.active update_runner(shared_runner.id, admin, paused: active) @@ -389,14 +389,14 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(shared_runner.reload.active).to eq(!active) end - it 'runner tag list' do + it 'runner tag list', :aggregate_failures do update_runner(shared_runner.id, admin, tag_list: ['ruby2.1', 'pgsql', 'mysql']) expect(response).to have_gitlab_http_status(:ok) expect(shared_runner.reload.tag_list).to include('ruby2.1', 'pgsql', 'mysql') end - it 'unrelated runner attribute on an existing runner with too many tags' do + it 'unrelated runner attribute on an existing runner with too many tags', :aggregate_failures do # This test ensures that it is possible to update any attribute on a runner that currently fails the # validation that ensures that there aren't too many tags associated with a runner existing_invalid_shared_runner = build(:ci_runner, :instance, tag_list: (1..::Ci::Runner::TAG_LIST_MAX_LENGTH + 1).map { |i| "tag#{i}" }) @@ -409,7 +409,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(existing_invalid_shared_runner.reload.active).to eq(!active) end - it 'runner untagged flag' do + it 'runner untagged flag', :aggregate_failures do # Ensure tag list is non-empty before setting untagged to false. update_runner(shared_runner.id, admin, tag_list: ['ruby2.1', 'pgsql', 'mysql']) update_runner(shared_runner.id, admin, run_untagged: 'false') @@ -418,28 +418,28 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(shared_runner.reload.run_untagged?).to be(false) end - it 'runner unlocked flag' do + it 'runner unlocked flag', :aggregate_failures do update_runner(shared_runner.id, admin, locked: 'true') expect(response).to have_gitlab_http_status(:ok) expect(shared_runner.reload.locked?).to be(true) end - it 'runner access level' do + it 'runner access level', :aggregate_failures do update_runner(shared_runner.id, admin, access_level: 'ref_protected') expect(response).to have_gitlab_http_status(:ok) expect(shared_runner.reload.ref_protected?).to be_truthy end - it 'runner maximum timeout' do + it 'runner maximum timeout', :aggregate_failures do update_runner(shared_runner.id, admin, maximum_timeout: 1234) expect(response).to have_gitlab_http_status(:ok) expect(shared_runner.reload.maximum_timeout).to eq(1234) end - it 'fails with no parameters' do + it 'fails with no parameters', :aggregate_failures do put api("/runners/#{shared_runner.id}", admin) shared_runner.reload @@ -448,7 +448,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when runner is shared' do - it 'updates runner' do + it 'updates runner', :aggregate_failures do description = shared_runner.description active = shared_runner.active runner_queue_value = shared_runner.ensure_runner_queue_value @@ -476,7 +476,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when runner is not shared' do - it 'updates runner' do + it 'updates runner', :aggregate_failures do description = project_runner.description runner_queue_value = project_runner.ensure_runner_queue_value @@ -498,14 +498,16 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end def update_runner(id, user, args) - put api("/runners/#{id}", user), params: args + put api("/runners/#{id}", user, admin_mode: true), params: args end end context 'authorized user' do + let_it_be(:params) { { description: 'test' } } + context 'when runner is shared' do it 'does not update runner' do - put api("/runners/#{shared_runner.id}", user), params: { description: 'test' } + put api("/runners/#{shared_runner.id}", user), params: params expect(response).to have_gitlab_http_status(:forbidden) end @@ -518,12 +520,11 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(response).to have_gitlab_http_status(:forbidden) end - it 'updates project runner with access to it' do + it 'updates project runner with access to it', :aggregate_failures do description = project_runner.description - put api("/runners/#{project_runner.id}", admin), params: { description: 'test' } + put api("/runners/#{project_runner.id}", admin, admin_mode: true), params: params project_runner.reload - expect(response).to have_gitlab_http_status(:ok) expect(project_runner.description).to eq('test') expect(project_runner.description).not_to eq(description) end @@ -542,13 +543,13 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do describe 'DELETE /runners/:id' do context 'admin user' do context 'when runner is shared' do - it 'deletes runner' do + it 'deletes runner', :aggregate_failures do expect_next_instance_of(Ci::Runners::UnregisterRunnerService, shared_runner, admin) do |service| expect(service).to receive(:execute).once.and_call_original end expect do - delete api("/runners/#{shared_runner.id}", admin) + delete api("/runners/#{shared_runner.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:no_content) end.to change { ::Ci::Runner.instance_type.count }.by(-1) @@ -560,25 +561,25 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when runner is not shared' do - it 'deletes used project runner' do + it 'deletes used project runner', :aggregate_failures do expect_next_instance_of(Ci::Runners::UnregisterRunnerService, project_runner, admin) do |service| expect(service).to receive(:execute).once.and_call_original end expect do - delete api("/runners/#{project_runner.id}", admin) + delete api("/runners/#{project_runner.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:no_content) end.to change { ::Ci::Runner.project_type.count }.by(-1) end end - it 'returns 404 if runner does not exist' do + it 'returns 404 if runner does not exist', :aggregate_failures do allow_next_instance_of(Ci::Runners::UnregisterRunnerService) do |service| expect(service).not_to receive(:execute) end - delete api('/runners/0', admin) + delete api('/runners/0', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -603,7 +604,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(response).to have_gitlab_http_status(:forbidden) end - it 'deletes project runner for one owned project' do + it 'deletes project runner for one owned project', :aggregate_failures do expect do delete api("/runners/#{project_runner.id}", user) @@ -658,7 +659,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'unauthorized user' do - it 'does not delete project runner' do + it 'does not delete project runner', :aggregate_failures do allow_next_instance_of(Ci::Runners::UnregisterRunnerService) do |service| expect(service).not_to receive(:execute) end @@ -672,31 +673,31 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do describe 'POST /runners/:id/reset_authentication_token' do context 'admin user' do - it 'resets shared runner authentication token' do + it 'resets shared runner authentication token', :aggregate_failures do expect do - post api("/runners/#{shared_runner.id}/reset_authentication_token", admin) + post api("/runners/#{shared_runner.id}/reset_authentication_token", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:success) expect(json_response).to eq({ 'token' => shared_runner.reload.token, 'token_expires_at' => nil }) end.to change { shared_runner.reload.token } end - it 'returns 404 if runner does not exist' do - post api('/runners/0/reset_authentication_token', admin) + it 'returns 404 if runner does not exist', :aggregate_failures do + post api('/runners/0/reset_authentication_token', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end end context 'authorized user' do - it 'does not reset project runner authentication token without access to it' do + it 'does not reset project runner authentication token without access to it', :aggregate_failures do expect do post api("/runners/#{project_runner.id}/reset_authentication_token", user2) expect(response).to have_gitlab_http_status(:forbidden) end.not_to change { project_runner.reload.token } end - it 'resets project runner authentication token for owned project' do + it 'resets project runner authentication token for owned project', :aggregate_failures do expect do post api("/runners/#{project_runner.id}/reset_authentication_token", user) @@ -705,7 +706,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end.to change { project_runner.reload.token } end - it 'does not reset group runner authentication token with guest access' do + it 'does not reset group runner authentication token with guest access', :aggregate_failures do expect do post api("/runners/#{group_runner_a.id}/reset_authentication_token", group_guest) @@ -713,7 +714,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end.not_to change { group_runner_a.reload.token } end - it 'does not reset group runner authentication token with reporter access' do + it 'does not reset group runner authentication token with reporter access', :aggregate_failures do expect do post api("/runners/#{group_runner_a.id}/reset_authentication_token", group_reporter) @@ -721,7 +722,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end.not_to change { group_runner_a.reload.token } end - it 'does not reset group runner authentication token with developer access' do + it 'does not reset group runner authentication token with developer access', :aggregate_failures do expect do post api("/runners/#{group_runner_a.id}/reset_authentication_token", group_developer) @@ -729,7 +730,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end.not_to change { group_runner_a.reload.token } end - it 'does not reset group runner authentication token with maintainer access' do + it 'does not reset group runner authentication token with maintainer access', :aggregate_failures do expect do post api("/runners/#{group_runner_a.id}/reset_authentication_token", group_maintainer) @@ -737,7 +738,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end.not_to change { group_runner_a.reload.token } end - it 'resets group runner authentication token with owner access' do + it 'resets group runner authentication token with owner access', :aggregate_failures do expect do post api("/runners/#{group_runner_a.id}/reset_authentication_token", user) @@ -746,7 +747,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end.to change { group_runner_a.reload.token } end - it 'resets group runner authentication token with owner access with expiration time', :freeze_time do + it 'resets group runner authentication token with owner access with expiration time', :aggregate_failures, :freeze_time do expect(group_runner_a.reload.token_expires_at).to be_nil group.update!(runner_token_expiration_interval: 5.days) @@ -763,7 +764,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'unauthorized user' do - it 'does not reset authentication token' do + it 'does not reset authentication token', :aggregate_failures do expect do post api("/runners/#{shared_runner.id}/reset_authentication_token") @@ -783,8 +784,8 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context 'admin user' do context 'when runner exists' do context 'when runner is shared' do - it 'return jobs' do - get api("/runners/#{shared_runner.id}/jobs", admin) + it 'return jobs', :aggregate_failures do + get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -795,8 +796,8 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when runner is a project runner' do - it 'return jobs' do - get api("/runners/#{project_runner.id}/jobs", admin) + it 'return jobs', :aggregate_failures do + get api("/runners/#{project_runner.id}/jobs", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -806,7 +807,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when user does not have authorization to see all jobs' do - it 'shows only jobs it has permission to see' do + it 'shows only jobs it has permission to see', :aggregate_failures do create(:ci_build, :running, runner: two_projects_runner, project: project) create(:ci_build, :running, runner: two_projects_runner, project: project2) @@ -824,8 +825,8 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when valid status is provided' do - it 'return filtered jobs' do - get api("/runners/#{project_runner.id}/jobs?status=failed", admin) + it 'return filtered jobs', :aggregate_failures do + get api("/runners/#{project_runner.id}/jobs?status=failed", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -838,8 +839,8 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context 'when valid order_by is provided' do context 'when sort order is not specified' do - it 'return jobs in descending order' do - get api("/runners/#{project_runner.id}/jobs?order_by=id", admin) + it 'return jobs in descending order', :aggregate_failures do + get api("/runners/#{project_runner.id}/jobs?order_by=id", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -851,8 +852,8 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when sort order is specified as asc' do - it 'return jobs sorted in ascending order' do - get api("/runners/#{project_runner.id}/jobs?order_by=id&sort=asc", admin) + it 'return jobs sorted in ascending order', :aggregate_failures do + get api("/runners/#{project_runner.id}/jobs?order_by=id&sort=asc", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -866,7 +867,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context 'when invalid status is provided' do it 'return 400' do - get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin) + get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -874,7 +875,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context 'when invalid order_by is provided' do it 'return 400' do - get api("/runners/#{project_runner.id}/jobs?order_by=non-existing", admin) + get api("/runners/#{project_runner.id}/jobs?order_by=non-existing", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -882,7 +883,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context 'when invalid sort is provided' do it 'return 400' do - get api("/runners/#{project_runner.id}/jobs?sort=non-existing", admin) + get api("/runners/#{project_runner.id}/jobs?sort=non-existing", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -890,16 +891,16 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end it 'avoids N+1 DB queries' do - get api("/runners/#{shared_runner.id}/jobs", admin) + get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true) control = ActiveRecord::QueryRecorder.new do - get api("/runners/#{shared_runner.id}/jobs", admin) + get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true) end create(:ci_build, :failed, runner: shared_runner, project: project) expect do - get api("/runners/#{shared_runner.id}/jobs", admin) + get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true) end.not_to exceed_query_limit(control.count) end @@ -925,12 +926,12 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do ]).once.and_call_original end - get api("/runners/#{shared_runner.id}/jobs", admin), params: { per_page: 2, order_by: 'id', sort: 'desc' } + get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true), params: { per_page: 2, order_by: 'id', sort: 'desc' } end context "when runner doesn't exist" do it 'returns 404' do - get api('/runners/0/jobs', admin) + get api('/runners/0/jobs', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -948,7 +949,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when runner is a project runner' do - it 'return jobs' do + it 'return jobs', :aggregate_failures do get api("/runners/#{project_runner.id}/jobs", user) expect(response).to have_gitlab_http_status(:ok) @@ -960,7 +961,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when valid status is provided' do - it 'return filtered jobs' do + it 'return filtered jobs', :aggregate_failures do get api("/runners/#{project_runner.id}/jobs?status=failed", user) expect(response).to have_gitlab_http_status(:ok) @@ -1027,8 +1028,8 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do describe 'GET /projects/:id/runners' do context 'authorized user with maintainer privileges' do - it 'returns response status and headers' do - get api('/runners/all', admin) + it 'returns response status and headers', :aggregate_failures do + get api('/runners/all', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -1044,7 +1045,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do ] end - it 'filters runners by scope' do + it 'filters runners by scope', :aggregate_failures do get api("/projects/#{project.id}/runners?scope=specific", user) expect(response).to have_gitlab_http_status(:ok) @@ -1102,7 +1103,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do expect(response).to have_gitlab_http_status(:bad_request) end - it 'filters runners by tag_list' do + it 'filters runners by tag_list', :aggregate_failures do create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2]) create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2']) @@ -1183,7 +1184,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end end - it 'filters runners by tag_list' do + it 'filters runners by tag_list', :aggregate_failures do create(:ci_runner, :group, description: 'Runner tagged with tag1 and tag2', groups: [group], tag_list: %w[tag1 tag2]) create(:ci_runner, :group, description: 'Runner tagged with tag2', groups: [group], tag_list: %w[tag1]) @@ -1203,21 +1204,21 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context 'authorized user' do let_it_be(:project_runner2) { create(:ci_runner, :project, projects: [project2]) } - it 'enables project runner' do + it 'enables project runner', :aggregate_failures do expect do post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id } end.to change { project.runners.count }.by(+1) expect(response).to have_gitlab_http_status(:created) end - it 'avoids changes when enabling already enabled runner' do + it 'avoids changes when enabling already enabled runner', :aggregate_failures do expect do post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner.id } end.to change { project.runners.count }.by(0) expect(response).to have_gitlab_http_status(:bad_request) end - it 'does not enable locked runner' do + it 'does not enable locked runner', :aggregate_failures do project_runner2.update!(locked: true) expect do @@ -1243,9 +1244,9 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do context 'when project runner is used' do let!(:new_project_runner) { create(:ci_runner, :project) } - it 'enables any project runner' do + it 'enables any project runner', :aggregate_failures do expect do - post api("/projects/#{project.id}/runners", admin), params: { runner_id: new_project_runner.id } + post api("/projects/#{project.id}/runners", admin, admin_mode: true), params: { runner_id: new_project_runner.id } end.to change { project.runners.count }.by(+1) expect(response).to have_gitlab_http_status(:created) end @@ -1255,9 +1256,9 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do create(:plan_limits, :default_plan, ci_registered_project_runners: 1) end - it 'does not enable project runner' do + it 'does not enable project runner', :aggregate_failures do expect do - post api("/projects/#{project.id}/runners", admin), params: { runner_id: new_project_runner.id } + post api("/projects/#{project.id}/runners", admin, admin_mode: true), params: { runner_id: new_project_runner.id } end.not_to change { project.runners.count } expect(response).to have_gitlab_http_status(:bad_request) end @@ -1266,7 +1267,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end it 'raises an error when no runner_id param is provided' do - post api("/projects/#{project.id}/runners", admin) + post api("/projects/#{project.id}/runners", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -1316,7 +1317,7 @@ RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do end context 'when runner have one associated projects' do - it "does not disable project's runner" do + it "does not disable project's runner", :aggregate_failures do expect do delete api("/projects/#{project.id}/runners/#{project_runner.id}", user) end.to change { project.runners.count }.by(0) diff --git a/spec/requests/api/ci/variables_spec.rb b/spec/requests/api/ci/variables_spec.rb index 0f9f1bc80d6..5ea9104cb15 100644 --- a/spec/requests/api/ci/variables_spec.rb +++ b/spec/requests/api/ci/variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Variables, feature_category: :pipeline_authoring do +RSpec.describe API::Ci::Variables, feature_category: :pipeline_composition do let(:user) { create(:user) } let(:user2) { create(:user) } let!(:project) { create(:project, creator_id: user.id) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index bcc27a80cf8..b4bc4507021 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -132,6 +132,42 @@ RSpec.describe API::Commits, feature_category: :source_code_management do it_behaves_like 'project commits' end + context 'with author parameter' do + let(:params) { { author: 'Zaporozhets' } } + + it 'returns only this author commits' do + get api(route, user), params: params + + expect(response).to have_gitlab_http_status(:ok) + + author_names = json_response.map { |commit| commit['author_name'] }.uniq + + expect(author_names).to contain_exactly('Dmitriy Zaporozhets') + end + + context 'when author is missing' do + let(:params) { { author: '' } } + + it 'returns all commits' do + get api(route, user), params: params + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq(20) + end + end + + context 'when author does not exists' do + let(:params) { { author: 'does not exist' } } + + it 'returns an empty list' do + get api(route, user), params: params + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq([]) + end + end + end + context 'when repository does not exist' do let(:project) { create(:project, creator: user, path: 'my.project') } @@ -425,6 +461,27 @@ RSpec.describe API::Commits, feature_category: :source_code_management do describe "POST /projects/:id/repository/commits" do let!(:url) { "/projects/#{project_id}/repository/commits" } + context 'when unauthenticated', 'and project is public' do + let_it_be(:project) { create(:project, :public, :repository) } + let(:params) do + { + branch: 'master', + commit_message: 'message', + actions: [ + { + action: 'create', + file_path: '/test.rb', + content: 'puts 8' + } + ] + } + end + + it_behaves_like '401 response' do + let(:request) { post api(url), params: params } + end + end + it 'returns a 403 unauthorized for user without permissions' do post api(url, guest) @@ -523,7 +580,6 @@ RSpec.describe API::Commits, feature_category: :source_code_management do let(:property) { 'g_edit_by_web_ide' } let(:label) { 'usage_activity_by_stage_monthly.create.action_monthly_active_users_ide_edit' } let(:context) { [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context] } - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } end context 'counts.web_ide_commits Snowplow event tracking' do @@ -1776,7 +1832,7 @@ RSpec.describe API::Commits, feature_category: :source_code_management do context 'when unauthenticated', 'and project is public' do let_it_be(:project) { create(:project, :public, :repository) } - it_behaves_like '403 response' do + it_behaves_like '401 response' do let(:request) { post api(route), params: { branch: 'master' } } end end @@ -1956,7 +2012,7 @@ RSpec.describe API::Commits, feature_category: :source_code_management do context 'when unauthenticated', 'and project is public' do let_it_be(:project) { create(:project, :public, :repository) } - it_behaves_like '403 response' do + it_behaves_like '401 response' do let(:request) { post api(route), params: { branch: branch } } end end diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb index 0c726d46a01..2bb2ffa03c4 100644 --- a/spec/requests/api/composer_packages_spec.rb +++ b/spec/requests/api/composer_packages_spec.rb @@ -504,7 +504,11 @@ RSpec.describe API::ComposerPackages, feature_category: :package_registry do include_context 'Composer user type', params[:user_role], params[:member] do if params[:expected_status] == :success let(:snowplow_gitlab_standard_context) do - { project: project, namespace: project.namespace, property: 'i_package_composer_user' } + if user_role == :anonymous || (project_visibility_level == 'PUBLIC' && user_token == false) + { project: project, namespace: project.namespace, property: 'i_package_composer_user' } + else + { project: project, namespace: project.namespace, property: 'i_package_composer_user', user: user } + end end it_behaves_like 'a package tracking event', described_class.name, 'pull_package' diff --git a/spec/requests/api/debian_group_packages_spec.rb b/spec/requests/api/debian_group_packages_spec.rb index 0c80b7d830f..25b99862100 100644 --- a/spec/requests/api/debian_group_packages_spec.rb +++ b/spec/requests/api/debian_group_packages_spec.rb @@ -31,9 +31,11 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/Packages" } + let(:target_component_file) { component_file } + let(:target_component_name) { component.name } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/binary-#{architecture.name}/Packages" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Packages file/ + it_behaves_like 'Debian packages index endpoint', /Description: This is an incomplete Packages file/ end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/Packages.gz' do @@ -43,27 +45,37 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/by-hash/SHA256/:file_sha256' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/by-hash/SHA256/#{component_file_older_sha256.file_sha256}" } + let(:target_component_file) { component_file_older_sha256 } + let(:target_component_name) { component.name } + let(:target_sha256) { target_component_file.file_sha256 } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/binary-#{architecture.name}/by-hash/SHA256/#{target_sha256}" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/ end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/source/Sources' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component.name}/source/Sources" } + let(:target_component_file) { component_file_sources } + let(:target_component_name) { component.name } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/source/Sources" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Sources file/ + it_behaves_like 'Debian packages index endpoint', /^Description: This is an incomplete Sources file$/ end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/source/by-hash/SHA256/:file_sha256' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component.name}/source/by-hash/SHA256/#{component_file_sources_older_sha256.file_sha256}" } + let(:target_component_file) { component_file_sources_older_sha256 } + let(:target_component_name) { component.name } + let(:target_sha256) { target_component_file.file_sha256 } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/source/by-hash/SHA256/#{target_sha256}" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/ end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/Packages' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/Packages" } + let(:target_component_file) { component_file_di } + let(:target_component_name) { component.name } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/debian-installer/binary-#{architecture.name}/Packages" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete D-I Packages file/ + it_behaves_like 'Debian packages index endpoint', /Description: This is an incomplete D-I Packages file/ end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/Packages.gz' do @@ -73,9 +85,12 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/by-hash/SHA256/:file_sha256' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/by-hash/SHA256/#{component_file_di_older_sha256.file_sha256}" } + let(:target_component_file) { component_file_di_older_sha256 } + let(:target_component_name) { component.name } + let(:target_sha256) { target_component_file.file_sha256 } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{target_component_name}/debian-installer/binary-#{architecture.name}/by-hash/SHA256/#{target_sha256}" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/ end describe 'GET groups/:id/-/packages/debian/pool/:codename/:project_id/:letter/:package_name/:package_version/:file_name' do @@ -89,6 +104,7 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do 'sample_1.2.3~alpha2.dsc' | /^Format: 3.0 \(native\)/ 'libsample0_1.2.3~alpha2_amd64.deb' | /^!<arch>/ 'sample-udeb_1.2.3~alpha2_amd64.udeb' | /^!<arch>/ + 'sample-ddeb_1.2.3~alpha2_amd64.ddeb' | /^!<arch>/ 'sample_1.2.3~alpha2_amd64.buildinfo' | /Build-Tainted-By/ 'sample_1.2.3~alpha2_amd64.changes' | /urgency=medium/ end diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb index 46f79efd928..e9ad39a08ab 100644 --- a/spec/requests/api/debian_project_packages_spec.rb +++ b/spec/requests/api/debian_project_packages_spec.rb @@ -44,9 +44,11 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/Packages" } + let(:target_component_file) { component_file } + let(:target_component_name) { component.name } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/binary-#{architecture.name}/Packages" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Packages file/ + it_behaves_like 'Debian packages index endpoint', /Description: This is an incomplete Packages file/ it_behaves_like 'accept GET request on private project with access to package registry for everyone' end @@ -57,30 +59,40 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/by-hash/SHA256/:file_sha256' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/by-hash/SHA256/#{component_file_older_sha256.file_sha256}" } + let(:target_component_file) { component_file_older_sha256 } + let(:target_component_name) { component.name } + let(:target_sha256) { target_component_file.file_sha256 } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/binary-#{architecture.name}/by-hash/SHA256/#{target_sha256}" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/ it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/source/Sources' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/source/Sources" } + let(:target_component_file) { component_file_sources } + let(:target_component_name) { component.name } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/source/Sources" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Sources file/ + it_behaves_like 'Debian packages index endpoint', /^Description: This is an incomplete Sources file$/ it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/source/by-hash/SHA256/:file_sha256' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/source/by-hash/SHA256/#{component_file_sources_older_sha256.file_sha256}" } + let(:target_component_file) { component_file_sources_older_sha256 } + let(:target_component_name) { component.name } + let(:target_sha256) { target_component_file.file_sha256 } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/source/by-hash/SHA256/#{target_sha256}" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/ it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/Packages' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/Packages" } + let(:target_component_file) { component_file_di } + let(:target_component_name) { component.name } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/debian-installer/binary-#{architecture.name}/Packages" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete D-I Packages file/ + it_behaves_like 'Debian packages index endpoint', /Description: This is an incomplete D-I Packages file/ it_behaves_like 'accept GET request on private project with access to package registry for everyone' end @@ -91,9 +103,12 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/by-hash/SHA256/:file_sha256' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/by-hash/SHA256/#{component_file_di_older_sha256.file_sha256}" } + let(:target_component_file) { component_file_di_older_sha256 } + let(:target_component_name) { component.name } + let(:target_sha256) { target_component_file.file_sha256 } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{target_component_name}/debian-installer/binary-#{architecture.name}/by-hash/SHA256/#{target_sha256}" } - it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'Debian packages index sha256 endpoint', /^Other SHA256$/ it_behaves_like 'accept GET request on private project with access to package registry for everyone' end @@ -108,6 +123,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d 'sample_1.2.3~alpha2.dsc' | /^Format: 3.0 \(native\)/ 'libsample0_1.2.3~alpha2_amd64.deb' | /^!<arch>/ 'sample-udeb_1.2.3~alpha2_amd64.udeb' | /^!<arch>/ + 'sample-ddeb_1.2.3~alpha2_amd64.ddeb' | /^!<arch>/ 'sample_1.2.3~alpha2_amd64.buildinfo' | /Build-Tainted-By/ 'sample_1.2.3~alpha2_amd64.changes' | /urgency=medium/ end @@ -162,7 +178,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d let(:extra_params) { { distribution: distribution.codename, component: 'main' } } it_behaves_like "Debian packages upload request", :bad_request, - /^file_name Only debs and udebs can be directly added to a distribution$/ + /^file_name Only debs, udebs and ddebs can be directly added to a distribution$/ end end end diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 5116f074894..888220c2251 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'doorkeeper access', feature_category: :authentication_and_authorization do +RSpec.describe 'doorkeeper access', feature_category: :system_access do let!(:user) { create(:user) } let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) } let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" } diff --git a/spec/requests/api/draft_notes_spec.rb b/spec/requests/api/draft_notes_spec.rb index e8f519e004d..d239853ac1d 100644 --- a/spec/requests/api/draft_notes_spec.rb +++ b/spec/requests/api/draft_notes_spec.rb @@ -8,11 +8,16 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do let_it_be(:project) { create(:project, :public) } let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } + let_it_be(:private_project) { create(:project, :private) } + let_it_be(:private_merge_request) do + create(:merge_request, source_project: private_project, target_project: private_project) + end + let_it_be(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) } let!(:draft_note_by_current_user) { create(:draft_note, merge_request: merge_request, author: user) } let!(:draft_note_by_random_user) { create(:draft_note, merge_request: merge_request) } - let_it_be(:api_stub) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}" } + let_it_be(:base_url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/draft_notes" } before do project.add_developer(user) @@ -20,13 +25,13 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do describe "Get a list of merge request draft notes" do it "returns 200 OK status" do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/draft_notes", user) + get api(base_url, user) expect(response).to have_gitlab_http_status(:ok) end it "returns only draft notes authored by the current user" do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/draft_notes", user) + get api(base_url, user) draft_note_ids = json_response.pluck("id") @@ -40,7 +45,7 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do context "when requesting an existing draft note by the user" do before do get api( - "/projects/#{project.id}/merge_requests/#{merge_request.iid}/draft_notes/#{draft_note_by_current_user.id}", + "#{base_url}/#{draft_note_by_current_user.id}", user ) end @@ -56,7 +61,7 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do context "when requesting a non-existent draft note" do it "returns a 404 Not Found response" do get api( - "/projects/#{project.id}/merge_requests/#{merge_request.iid}/draft_notes/#{DraftNote.last.id + 1}", + "#{base_url}/#{DraftNote.last.id + 1}", user ) @@ -67,7 +72,7 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do context "when requesting an existing draft note by another user" do it "returns a 404 Not Found response" do get api( - "/projects/#{project.id}/merge_requests/#{merge_request.iid}/draft_notes/#{draft_note_by_random_user.id}", + "#{base_url}/#{draft_note_by_random_user.id}", user ) @@ -83,7 +88,7 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do before do delete api( - "/projects/#{project.id}/merge_requests/#{merge_request.iid}/draft_notes/#{draft_note_by_current_user.id}", + "#{base_url}/#{draft_note_by_current_user.id}", user ) end @@ -100,7 +105,7 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do context "when deleting a non-existent draft note" do it "returns a 404 Not Found" do delete api( - "/projects/#{project.id}/merge_requests/#{merge_request.iid}/draft_notes/#{non_existing_record_id}", + "#{base_url}/#{non_existing_record_id}", user ) @@ -111,7 +116,7 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do context "when deleting a draft note by a different user" do it "returns a 404 Not Found" do delete api( - "/projects/#{project.id}/merge_requests/#{merge_request.iid}/draft_notes/#{draft_note_by_random_user.id}", + "#{base_url}/#{draft_note_by_random_user.id}", user ) @@ -120,10 +125,152 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do end end + def create_draft_note(params = {}, url = base_url) + post api(url, user), params: params + end + + describe "Create a new draft note" do + let(:basic_create_params) do + { + note: "Example body string" + } + end + + context "when creating a new draft note" do + context "with required params" do + it "returns 201 Created status" do + create_draft_note(basic_create_params) + + expect(response).to have_gitlab_http_status(:created) + end + + it "creates a new draft note with the submitted params" do + expect { create_draft_note(basic_create_params) }.to change { DraftNote.count }.by(1) + + expect(json_response["note"]).to eq(basic_create_params[:note]) + expect(json_response["merge_request_id"]).to eq(merge_request.id) + expect(json_response["author_id"]).to eq(user.id) + end + end + + context "without required params" do + it "returns 400 Bad Request status" do + create_draft_note({}) + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context "when providing a non-existing commit_id" do + it "returns a 400 Bad Request" do + create_draft_note( + basic_create_params.merge( + commit_id: 'bad SHA' + ) + ) + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context "when targeting a merge request the user doesn't have access to" do + it "returns a 404 Not Found" do + create_draft_note( + basic_create_params, + "/projects/#{private_project.id}/merge_requests/#{private_merge_request.iid}" + ) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context "when attempting to resolve a disscussion" do + context "when providing a non-existant ID" do + it "returns a 400 Bad Request" do + create_draft_note( + basic_create_params.merge( + resolve_discussion: true, + in_reply_to_discussion_id: non_existing_record_id + ) + ) + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context "when not providing an ID" do + it "returns a 400 Bad Request" do + create_draft_note(basic_create_params.merge(resolve_discussion: true)) + + expect(response).to have_gitlab_http_status(:bad_request) + end + + it "returns a validation error message" do + create_draft_note(basic_create_params.merge(resolve_discussion: true)) + + expect(response.body) + .to eq("{\"message\":{\"base\":[\"User is not allowed to resolve thread\"]}}") + end + end + end + end + end + + def update_draft_note(params = {}, url = base_url) + put api("#{url}/#{draft_note_by_current_user.id}", user), params: params + end + + describe "Update a draft note" do + let(:basic_update_params) do + { + note: "Example updated body string" + } + end + + context "when updating an existing draft note" do + context "with required params" do + it "returns 200 Success status" do + update_draft_note(basic_update_params) + + expect(response).to have_gitlab_http_status(:success) + end + + it "updates draft note with the new content" do + update_draft_note(basic_update_params) + + expect(json_response["note"]).to eq(basic_update_params[:note]) + end + end + + context "without including an update to the note body" do + it "returns the draft note with no changes" do + expect { update_draft_note({}) } + .not_to change { draft_note_by_current_user.note } + end + end + + context "when updating a non-existent draft note" do + it "returns a 404 Not Found" do + put api("#{base_url}/#{non_existing_record_id}", user), params: basic_update_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context "when updating a draft note by a different user" do + it "returns a 404 Not Found" do + put api("#{base_url}/#{draft_note_by_random_user.id}", user), params: basic_update_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end + describe "Publishing a draft note" do let(:publish_draft_note) do put api( - "#{api_stub}/draft_notes/#{draft_note_by_current_user.id}/publish", + "#{base_url}/#{draft_note_by_current_user.id}/publish", user ) end @@ -144,7 +291,7 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do context "when publishing a non-existent draft note" do it "returns a 404 Not Found" do put api( - "#{api_stub}/draft_notes/#{non_existing_record_id}/publish", + "#{base_url}/#{non_existing_record_id}/publish", user ) @@ -155,7 +302,7 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do context "when publishing a draft note by a different user" do it "returns a 404 Not Found" do put api( - "#{api_stub}/draft_notes/#{draft_note_by_random_user.id}/publish", + "#{base_url}/#{draft_note_by_random_user.id}/publish", user ) diff --git a/spec/requests/api/error_tracking/project_settings_spec.rb b/spec/requests/api/error_tracking/project_settings_spec.rb index 5906cdf105a..3b01dec6f9c 100644 --- a/spec/requests/api/error_tracking/project_settings_spec.rb +++ b/spec/requests/api/error_tracking/project_settings_spec.rb @@ -38,7 +38,7 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra end end - shared_examples 'returns 404' do + shared_examples 'returns no project settings' do it 'returns no project settings' do make_request @@ -48,6 +48,57 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra end end + shared_examples 'returns 400' do + it 'rejects request' do + make_request + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + shared_examples 'returns 401' do + it 'rejects request' do + make_request + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + shared_examples 'returns 403' do + it 'rejects request' do + make_request + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + shared_examples 'returns 404' do + it 'rejects request' do + make_request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + shared_examples 'returns 400 with `integrated` param required or invalid' do |error| + it 'returns 400' do + make_request + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']) + .to eq(error) + end + end + + shared_examples "returns error from UpdateService" do + it "returns errors" do + make_request + + expect(json_response['http_status']).to eq('forbidden') + expect(json_response['message']).to eq('An error occurred') + end + end + describe "PATCH /projects/:id/error_tracking/settings" do let(:params) { { active: false } } @@ -127,14 +178,34 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra end context 'without a project setting' do - let(:project) { create(:project) } + let_it_be(:project) { create(:project) } before do project.add_maintainer(user) end context 'patch settings' do - it_behaves_like 'returns 404' + it_behaves_like 'returns no project settings' + end + end + + context "when ::Projects::Operations::UpdateService responds with an error" do + before do + allow_next_instance_of(::Projects::Operations::UpdateService) do |service| + allow(service) + .to receive(:execute) + .and_return({ status: :error, message: 'An error occurred', http_status: :forbidden }) + end + end + + context "when integrated" do + let(:integrated) { true } + + it_behaves_like 'returns error from UpdateService' + end + + context "without integrated" do + it_behaves_like 'returns error from UpdateService' end end end @@ -145,11 +216,7 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra end context 'patch request' do - it 'returns 403' do - make_request - - expect(response).to have_gitlab_http_status(:forbidden) - end + it_behaves_like 'returns 403' end end @@ -159,21 +226,13 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra end context 'patch request' do - it 'returns 403' do - make_request - - expect(response).to have_gitlab_http_status(:forbidden) - end + it_behaves_like 'returns 403' end end context 'when authenticated as non-member' do context 'patch request' do - it 'returns 404' do - make_request - - expect(response).to have_gitlab_http_status(:not_found) - end + it_behaves_like 'returns 404' end end @@ -181,11 +240,7 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra let(:user) { nil } context 'patch request' do - it 'returns 401 for update request' do - make_request - - expect(response).to have_gitlab_http_status(:unauthorized) - end + it_behaves_like 'returns 401' end end end @@ -227,7 +282,7 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra end context 'get settings' do - it_behaves_like 'returns 404' + it_behaves_like 'returns no project settings' end end @@ -236,11 +291,7 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra project.add_reporter(user) end - it 'returns 403' do - make_request - - expect(response).to have_gitlab_http_status(:forbidden) - end + it_behaves_like 'returns 403' end context 'when authenticated as developer' do @@ -248,29 +299,138 @@ RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tra project.add_developer(user) end - it 'returns 403' do - make_request - - expect(response).to have_gitlab_http_status(:forbidden) - end + it_behaves_like 'returns 403' end context 'when authenticated as non-member' do - it 'returns 404' do - make_request - - expect(response).to have_gitlab_http_status(:not_found) - end + it_behaves_like 'returns 404' end context 'when unauthenticated' do let(:user) { nil } - it 'returns 401' do - make_request + it_behaves_like 'returns 401' + end + end + + describe "PUT /projects/:id/error_tracking/settings" do + let(:params) { { active: active, integrated: integrated } } + let(:active) { true } + let(:integrated) { true } + + def make_request + put api("/projects/#{project.id}/error_tracking/settings", user), params: params + end + + context 'when authenticated' do + context 'as maintainer' do + before do + project.add_maintainer(user) + end + + context "when integrated" do + let(:integrated) { true } + + context "with existing setting" do + let(:setting) { create(:project_error_tracking_setting, :integrated) } + let(:active) { false } + + it "updates a setting" do + expect { make_request }.not_to change { ErrorTracking::ProjectErrorTrackingSetting.count } + + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response).to eq( + "active" => false, + "api_url" => nil, + "integrated" => integrated, + "project_name" => nil, + "sentry_external_url" => nil + ) + end + end + + context "without setting" do + let(:active) { true } + let_it_be(:project) { create(:project) } + + it "creates a setting" do + expect { make_request }.to change { ErrorTracking::ProjectErrorTrackingSetting.count } + + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response).to eq( + "active" => true, + "api_url" => nil, + "integrated" => integrated, + "project_name" => nil, + "sentry_external_url" => nil + ) + end + end + + context "when ::Projects::Operations::UpdateService responds with an error" do + before do + allow_next_instance_of(::Projects::Operations::UpdateService) do |service| + allow(service) + .to receive(:execute) + .and_return({ status: :error, message: 'An error occurred', http_status: :forbidden }) + end + end + + it_behaves_like 'returns error from UpdateService' + end + end - expect(response).to have_gitlab_http_status(:unauthorized) + context "integrated_error_tracking feature disabled" do + let(:integrated) { true } + + before do + stub_feature_flags(integrated_error_tracking: false) + end + + it_behaves_like 'returns 404' + end + + context "when integrated param is invalid" do + let(:params) { { active: active, integrated: 'invalid_string' } } + + it_behaves_like 'returns 400 with `integrated` param required or invalid', 'integrated is invalid' + end + + context "when integrated param is missing" do + let(:params) { { active: active } } + + it_behaves_like 'returns 400 with `integrated` param required or invalid', 'integrated is missing' + end + end + + context 'as reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like 'returns 403' + end + + context "as developer" do + before do + project.add_developer(user) + end + + it_behaves_like 'returns 403' end + + context 'as non-member' do + it_behaves_like 'returns 404' + end + end + + context "when unauthorized" do + let(:user) { nil } + let(:integrated) { true } + + it_behaves_like 'returns 401' end end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index f4066c54c47..ed84e3e5f48 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -55,6 +55,11 @@ RSpec.describe API::Files, feature_category: :source_code_management do } end + let(:last_commit_for_path) do + Gitlab::Git::Commit + .last_for_path(project.repository, 'master', Addressable::URI.unencode_component(file_path)) + end + shared_context 'with author parameters' do let(:author_email) { 'user@example.org' } let(:author_name) { 'John Doe' } @@ -136,6 +141,12 @@ RSpec.describe API::Files, feature_category: :source_code_management do it 'caches sha256 of the content', :use_clean_rails_redis_caching do head api(route(file_path), current_user, **options), params: params + expect(Gitlab::Cache::Client).to receive(:build_with_metadata).with( + cache_identifier: 'API::Files#content_sha', + feature_category: :source_code_management, + backing_resource: :gitaly + ).and_call_original + expect(Rails.cache.fetch("blob_content_sha256:#{project.full_path}:#{response.headers['X-Gitlab-Blob-Id']}")) .to eq(content_sha256) @@ -829,7 +840,6 @@ RSpec.describe API::Files, feature_category: :source_code_management do expect_to_send_git_blob(api(url, current_user), params) expect(response.headers['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store, no-cache') - expect(response.headers['Pragma']).to eq('no-cache') expect(response.headers['Expires']).to eq('Fri, 01 Jan 1990 00:00:00 GMT') end @@ -1180,7 +1190,7 @@ RSpec.describe API::Files, feature_category: :source_code_management do end context 'when updating an existing file with stale last commit id' do - let(:params_with_stale_id) { params.merge(last_commit_id: 'stale') } + let(:params_with_stale_id) { params.merge(last_commit_id: last_commit_for_path.parent_id) } it 'returns a 400 bad request' do put api(route(file_path), user), params: params_with_stale_id @@ -1191,12 +1201,7 @@ RSpec.describe API::Files, feature_category: :source_code_management do end context 'with correct last commit id' do - let(:last_commit) do - Gitlab::Git::Commit - .last_for_path(project.repository, 'master', Addressable::URI.unencode_component(file_path)) - end - - let(:params_with_correct_id) { params.merge(last_commit_id: last_commit.id) } + let(:params_with_correct_id) { params.merge(last_commit_id: last_commit_for_path.id) } it 'updates existing file in project repo' do put api(route(file_path), user), params: params_with_correct_id @@ -1206,12 +1211,7 @@ RSpec.describe API::Files, feature_category: :source_code_management do end context 'when file path is invalid' do - let(:last_commit) do - Gitlab::Git::Commit - .last_for_path(project.repository, 'master', Addressable::URI.unencode_component(file_path)) - end - - let(:params_with_correct_id) { params.merge(last_commit_id: last_commit.id) } + let(:params_with_correct_id) { params.merge(last_commit_id: last_commit_for_path.id) } it 'returns a 400 bad request' do put api(route(invalid_file_path), user), params: params_with_correct_id @@ -1222,12 +1222,7 @@ RSpec.describe API::Files, feature_category: :source_code_management do end it_behaves_like 'when path is absolute' do - let(:last_commit) do - Gitlab::Git::Commit - .last_for_path(project.repository, 'master', Addressable::URI.unencode_component(file_path)) - end - - let(:params_with_correct_id) { params.merge(last_commit_id: last_commit.id) } + let(:params_with_correct_id) { params.merge(last_commit_id: last_commit_for_path.id) } subject { put api(route(absolute_path), user), params: params_with_correct_id } end diff --git a/spec/requests/api/freeze_periods_spec.rb b/spec/requests/api/freeze_periods_spec.rb index 170871706dc..a53db516940 100644 --- a/spec/requests/api/freeze_periods_spec.rb +++ b/spec/requests/api/freeze_periods_spec.rb @@ -12,14 +12,12 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do let(:last_freeze_period) { project.freeze_periods.last } describe 'GET /projects/:id/freeze_periods' do + let_it_be(:path) { "/projects/#{project.id}/freeze_periods" } + context 'when the user is the admin' do let!(:freeze_period) { create(:ci_freeze_period, project: project, created_at: 2.days.ago) } - it 'returns 200 HTTP status' do - get api("/projects/#{project.id}/freeze_periods", admin) - - expect(response).to have_gitlab_http_status(:ok) - end + it_behaves_like 'GET request permissions for admin mode when admin', :not_found end context 'when the user is the maintainer' do @@ -31,36 +29,26 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do let!(:freeze_period_1) { create(:ci_freeze_period, project: project, created_at: 2.days.ago) } let!(:freeze_period_2) { create(:ci_freeze_period, project: project, created_at: 1.day.ago) } - it 'returns 200 HTTP status' do - get api("/projects/#{project.id}/freeze_periods", user) + it 'returns freeze_periods ordered by created_at ascending', :aggregate_failures do + get api(path, user) expect(response).to have_gitlab_http_status(:ok) - end - - it 'returns freeze_periods ordered by created_at ascending' do - get api("/projects/#{project.id}/freeze_periods", user) - expect(json_response.count).to eq(2) expect(freeze_period_ids).to eq([freeze_period_1.id, freeze_period_2.id]) end it 'matches response schema' do - get api("/projects/#{project.id}/freeze_periods", user) + get api(path, user) expect(response).to match_response_schema('public_api/v4/freeze_periods') end end context 'when there are no freeze_periods' do - it 'returns 200 HTTP status' do - get api("/projects/#{project.id}/freeze_periods", user) + it 'returns 200 HTTP status with empty response', :aggregate_failures do + get api(path, user) expect(response).to have_gitlab_http_status(:ok) - end - - it 'returns an empty response' do - get api("/projects/#{project.id}/freeze_periods", user) - expect(json_response).to be_empty end end @@ -75,28 +63,21 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do create(:ci_freeze_period, project: project) end - it 'responds 403 Forbidden' do - get api("/projects/#{project.id}/freeze_periods", user) - - expect(response).to have_gitlab_http_status(:forbidden) + context 'and responds 403 Forbidden' do + it_behaves_like 'GET request permissions for admin mode when user', :forbidden do + let(:current_user) { user } + end end end context 'when user is not a project member' do - it 'responds 404 Not Found' do - get api("/projects/#{project.id}/freeze_periods", user) - - expect(response).to have_gitlab_http_status(:not_found) - end + it_behaves_like 'GET request permissions for admin mode when user', :not_found context 'when project is public' do let(:project) { create(:project, :public) } + let(:path) { "/projects/#{project.id}/freeze_periods" } - it 'responds 403 Forbidden' do - get api("/projects/#{project.id}/freeze_periods", user) - - expect(response).to have_gitlab_http_status(:forbidden) - end + it_behaves_like 'GET request permissions for admin mode when user', :forbidden end end end @@ -107,14 +88,12 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do create(:ci_freeze_period, project: project) end + let(:path) { "/projects/#{project.id}/freeze_periods/#{freeze_period.id}" } + context 'when the user is the admin' do let!(:freeze_period) { create(:ci_freeze_period, project: project, created_at: 2.days.ago) } - it 'responds 200 OK' do - get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", admin) - - expect(response).to have_gitlab_http_status(:ok) - end + it_behaves_like 'GET request permissions for admin mode when admin', :not_found end context 'when the user is the maintainer' do @@ -122,15 +101,10 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do project.add_maintainer(user) end - it 'responds 200 OK' do - get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user) + it 'returns a freeze period', :aggregate_failures do + get api(path, user) expect(response).to have_gitlab_http_status(:ok) - end - - it 'returns a freeze period' do - get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user) - expect(json_response).to include( 'id' => freeze_period.id, 'freeze_start' => freeze_period.freeze_start, @@ -139,7 +113,7 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do end it 'matches response schema' do - get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user) + get api(path, user) expect(response).to match_response_schema('public_api/v4/freeze_period') end @@ -150,28 +124,26 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do project.add_guest(user) end - it 'responds 403 Forbidden' do - get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user) - - expect(response).to have_gitlab_http_status(:forbidden) + context 'and responds 403 Forbidden' do + it_behaves_like 'GET request permissions for admin mode when user' do + let(:current_user) { user } + end end context 'when project is public' do let(:project) { create(:project, :public) } - context 'when freeze_period exists' do - it 'responds 403 Forbidden' do - get api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", user) - - expect(response).to have_gitlab_http_status(:forbidden) + context 'and responds 403 Forbidden when freeze_period exists' do + it_behaves_like 'GET request permissions for admin mode when user' do + let(:current_user) { user } end end - context 'when freeze_period does not exist' do - it 'responds 403 Forbidden' do - get api("/projects/#{project.id}/freeze_periods/0", user) + context 'and responds 403 Forbidden when freeze_period does not exist' do + let(:path) { "/projects/#{project.id}/freeze_periods/0" } - expect(response).to have_gitlab_http_status(:forbidden) + it_behaves_like 'GET request permissions for admin mode when user' do + let(:current_user) { user } end end end @@ -188,15 +160,13 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do } end - subject { post api("/projects/#{project.id}/freeze_periods", api_user), params: params } + let(:path) { "/projects/#{project.id}/freeze_periods" } - context 'when the user is the admin' do - let(:api_user) { admin } - - it 'accepts the request' do - subject + subject { post api(path, api_user), params: params } - expect(response).to have_gitlab_http_status(:created) + context 'when the user is the admin' do + it_behaves_like 'POST request permissions for admin mode when admin', :not_found do + let(:current_user) { admin } end end @@ -212,7 +182,7 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do expect(response).to have_gitlab_http_status(:created) end - it 'creates a new freeze period' do + it 'creates a new freeze period', :aggregate_failures do expect do subject end.to change { Ci::FreezePeriod.count }.by(1) @@ -268,10 +238,10 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do project.add_developer(user) end - it 'responds 403 Forbidden' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) + context 'and responds 403 Forbidden' do + it_behaves_like 'POST request permissions for admin mode when user' do + let(:current_user) { user } + end end end @@ -280,28 +250,22 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do project.add_reporter(user) end - it 'responds 403 Forbidden' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) + context 'and responds 403 Forbidden' do + it_behaves_like 'POST request permissions for admin mode when user' do + let(:current_user) { user } + end end end context 'when user is not a project member' do - it 'responds 403 Forbidden' do - subject - - expect(response).to have_gitlab_http_status(:not_found) + context 'and responds 403 Forbidden' do + it_behaves_like 'POST request permissions for admin mode when user', :not_found end context 'when project is public' do let(:project) { create(:project, :public) } - it 'responds 403 Forbidden' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) - end + it_behaves_like 'POST request permissions for admin mode when user', :forbidden end end end @@ -309,17 +273,12 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do describe 'PUT /projects/:id/freeze_periods/:freeze_period_id' do let(:params) { { freeze_start: '0 22 * * 5', freeze_end: '5 4 * * sun' } } let!(:freeze_period) { create :ci_freeze_period, project: project } + let(:path) { "/projects/#{project.id}/freeze_periods/#{freeze_period.id}" } - subject { put api("/projects/#{project.id}/freeze_periods/#{freeze_period.id}", api_user), params: params } + subject { put api(path, api_user), params: params } context 'when user is the admin' do - let(:api_user) { admin } - - it 'accepts the request' do - subject - - expect(response).to have_gitlab_http_status(:ok) - end + it_behaves_like 'PUT request permissions for admin mode when admin', :not_found end context 'when user is the maintainer' do @@ -367,27 +326,23 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do project.add_reporter(user) end - it 'responds 403 Forbidden' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) + context 'and responds 403 Forbidden' do + it_behaves_like 'PUT request permissions for admin mode when user' do + let(:current_user) { user } + end end end context 'when user is not a project member' do - it 'responds 404 Not Found' do - subject - - expect(response).to have_gitlab_http_status(:not_found) + context 'and responds 404 Not Found' do + it_behaves_like 'PUT request permissions for admin mode when user', :not_found end context 'when project is public' do let(:project) { create(:project, :public) } - it 'responds 403 Forbidden' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) + context 'and responds 403 Forbidden' do + it_behaves_like 'PUT request permissions for admin mode when user' end end end @@ -396,17 +351,12 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do describe 'DELETE /projects/:id/freeze_periods/:freeze_period_id' do let!(:freeze_period) { create :ci_freeze_period, project: project } let(:freeze_period_id) { freeze_period.id } + let(:path) { "/projects/#{project.id}/freeze_periods/#{freeze_period_id}" } - subject { delete api("/projects/#{project.id}/freeze_periods/#{freeze_period_id}", api_user) } + subject { delete api(path, api_user) } context 'when user is the admin' do - let(:api_user) { admin } - - it 'accepts the request' do - subject - - expect(response).to have_gitlab_http_status(:no_content) - end + it_behaves_like 'DELETE request permissions for admin mode when admin', failed_status_code: :not_found end context 'when user is the maintainer' do @@ -442,27 +392,23 @@ RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery do project.add_reporter(user) end - it 'responds 403 Forbidden' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) + context 'and responds 403 Forbidden' do + it_behaves_like 'DELETE request permissions for admin mode when user' do + let(:current_user) { user } + end end end context 'when user is not a project member' do - it 'responds 404 Not Found' do - subject - - expect(response).to have_gitlab_http_status(:not_found) + context 'and responds 404 Not Found' do + it_behaves_like 'DELETE request permissions for admin mode when user', :not_found end context 'when project is public' do let(:project) { create(:project, :public) } - it 'responds 403 Forbidden' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) + context 'and responds 403 Forbidden' do + it_behaves_like 'DELETE request permissions for admin mode when user' end end end diff --git a/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb b/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb new file mode 100644 index 00000000000..dc19f3bdc91 --- /dev/null +++ b/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'UserAchievements', feature_category: :user_profile do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :public) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + let_it_be(:user_achievement1) { create(:user_achievement, achievement: achievement, user: user) } + let_it_be(:user_achievement2) { create(:user_achievement, :revoked, achievement: achievement, user: user) } + let_it_be(:fields) do + <<~HEREDOC + id + achievements { + nodes { + userAchievements { + nodes { + id + achievement { + id + } + user { + id + } + awardedByUser { + id + } + revokedByUser { + id + } + } + } + } + } + HEREDOC + end + + let_it_be(:query) do + graphql_query_for('namespace', { full_path: group.full_path }, fields) + end + + before_all do + group.add_guest(user) + end + + before do + post_graphql(query, current_user: user) + end + + it_behaves_like 'a working graphql query' + + it 'returns all user_achievements' do + expect(graphql_data_at(:namespace, :achievements, :nodes, :userAchievements, :nodes)) + .to contain_exactly( + a_graphql_entity_for(user_achievement1), + a_graphql_entity_for(user_achievement2) + ) + end + + it 'can lookahead to eliminate N+1 queries', :use_clean_rails_memory_store_caching do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + post_graphql(query, current_user: user) + end.count + + user2 = create(:user) + create_list(:achievement, 3, namespace: group) do |a| + create(:user_achievement, achievement: a, user: user2) + end + + expect { post_graphql(query, current_user: user) }.not_to exceed_all_query_limit(control_count) + end + + context 'when the achievements feature flag is disabled' do + before do + stub_feature_flags(achievements: false) + post_graphql(query, current_user: user) + end + + specify { expect(graphql_data_at(:namespace, :achievements, :nodes, :userAchievements, :nodes)).to be_empty } + end +end diff --git a/spec/requests/api/graphql/ci/config_variables_spec.rb b/spec/requests/api/graphql/ci/config_variables_spec.rb index f76bb8ff837..d77e66d2239 100644 --- a/spec/requests/api/graphql/ci/config_variables_spec.rb +++ b/spec/requests/api/graphql/ci/config_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).ciConfigVariables(sha)', feature_category: :pipeline_authoring do +RSpec.describe 'Query.project(fullPath).ciConfigVariables(sha)', feature_category: :pipeline_composition do include GraphqlHelpers include ReactiveCachingHelpers diff --git a/spec/requests/api/graphql/ci/group_variables_spec.rb b/spec/requests/api/graphql/ci/group_variables_spec.rb index d78b30787c9..042f93e9779 100644 --- a/spec/requests/api/graphql/ci/group_variables_spec.rb +++ b/spec/requests/api/graphql/ci/group_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.group(fullPath).ciVariables', feature_category: :pipeline_authoring do +RSpec.describe 'Query.group(fullPath).ciVariables', feature_category: :pipeline_composition do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/ci/instance_variables_spec.rb b/spec/requests/api/graphql/ci/instance_variables_spec.rb index 5b65ae88426..286a7af3c01 100644 --- a/spec/requests/api/graphql/ci/instance_variables_spec.rb +++ b/spec/requests/api/graphql/ci/instance_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.ciVariables', feature_category: :pipeline_authoring do +RSpec.describe 'Query.ciVariables', feature_category: :pipeline_composition do include GraphqlHelpers let(:query) do diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb index 674407c0a0e..3f556a75869 100644 --- a/spec/requests/api/graphql/ci/jobs_spec.rb +++ b/spec/requests/api/graphql/ci/jobs_spec.rb @@ -260,6 +260,68 @@ RSpec.describe 'Query.project.pipeline', feature_category: :continuous_integrati end end + describe '.jobs.runnerMachine' do + let_it_be(:admin) { create(:admin) } + let_it_be(:runner_machine) { create(:ci_runner_machine, created_at: Time.current, contacted_at: Time.current) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:build) do + create(:ci_build, pipeline: pipeline, name: 'my test job', runner_machine: runner_machine) + end + + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + pipeline(iid: "#{pipeline.iid}") { + jobs { + nodes { + id + name + runnerMachine { + #{all_graphql_fields_for('CiRunnerMachine', excluded: [:runner], max_depth: 1)} + } + } + } + } + } + } + ) + end + + let(:jobs_graphql_data) { graphql_data_at(:project, :pipeline, :jobs, :nodes) } + + it 'returns the runner machine in each job of a pipeline' do + post_graphql(query, current_user: admin) + + expect(jobs_graphql_data).to contain_exactly( + a_graphql_entity_for( + build, + name: build.name, + runner_machine: a_graphql_entity_for( + runner_machine, + system_id: runner_machine.system_xid, + created_at: runner_machine.created_at.iso8601, + contacted_at: runner_machine.contacted_at.iso8601, + status: runner_machine.status.to_s.upcase + ) + ) + ) + end + + it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do + admin2 = create(:admin) + + control = ActiveRecord::QueryRecorder.new(skip_cached: false) do + post_graphql(query, current_user: admin) + end + + runner_machine2 = create(:ci_runner_machine) + create(:ci_build, pipeline: pipeline, name: 'my test job2', runner_machine: runner_machine2) + + expect { post_graphql(query, current_user: admin2) }.not_to exceed_all_query_limit(control) + end + end + describe '.jobs.count' do let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let_it_be(:successful_job) { create(:ci_build, :success, pipeline: pipeline) } diff --git a/spec/requests/api/graphql/ci/manual_variables_spec.rb b/spec/requests/api/graphql/ci/manual_variables_spec.rb index 921c69e535d..98d91e9ded0 100644 --- a/spec/requests/api/graphql/ci/manual_variables_spec.rb +++ b/spec/requests/api/graphql/ci/manual_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).pipelines.jobs.manualVariables', feature_category: :pipeline_authoring do +RSpec.describe 'Query.project(fullPath).pipelines.jobs.manualVariables', feature_category: :pipeline_composition do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/ci/project_variables_spec.rb b/spec/requests/api/graphql/ci/project_variables_spec.rb index 0ddcac89b34..947991a2e62 100644 --- a/spec/requests/api/graphql/ci/project_variables_spec.rb +++ b/spec/requests/api/graphql/ci/project_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).ciVariables', feature_category: :pipeline_authoring do +RSpec.describe 'Query.project(fullPath).ciVariables', feature_category: :pipeline_composition do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb index 986e3ce9e52..da71ee675b7 100644 --- a/spec/requests/api/graphql/ci/runner_spec.rb +++ b/spec/requests/api/graphql/ci/runner_spec.rb @@ -6,11 +6,13 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do include GraphqlHelpers let_it_be(:user) { create(:user, :admin) } + let_it_be(:another_admin) { create(:user, :admin) } let_it_be(:group) { create(:group) } let_it_be(:active_instance_runner) do - create(:ci_runner, :instance, + create(:ci_runner, :instance, :with_runner_machine, description: 'Runner 1', + creator: user, contacted_at: 2.hours.ago, active: true, version: 'adfe156', @@ -28,6 +30,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do let_it_be(:inactive_instance_runner) do create(:ci_runner, :instance, description: 'Runner 2', + creator: another_admin, contacted_at: 1.day.ago, active: false, version: 'adfe157', @@ -55,7 +58,9 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do end let_it_be(:project1) { create(:project) } - let_it_be(:active_project_runner) { create(:ci_runner, :project, projects: [project1]) } + let_it_be(:active_project_runner) do + create(:ci_runner, :project, :with_runner_machine, projects: [project1]) + end shared_examples 'runner details fetch' do let(:query) do @@ -77,6 +82,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do expect(runner_data).to match a_graphql_entity_for( runner, description: runner.description, + created_by: runner.creator ? a_graphql_entity_for(runner.creator) : nil, created_at: runner.created_at&.iso8601, contacted_at: runner.contacted_at&.iso8601, version: runner.version, @@ -107,15 +113,39 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do ), project_count: nil, admin_url: "http://localhost/admin/runners/#{runner.id}", + edit_admin_url: "http://localhost/admin/runners/#{runner.id}/edit", + register_admin_url: runner.registration_available? ? "http://localhost/admin/runners/#{runner.id}/register" : nil, user_permissions: { 'readRunner' => true, 'updateRunner' => true, 'deleteRunner' => true, 'assignRunner' => true - } + }, + machines: a_hash_including( + "count" => runner.runner_machines.count, + "nodes" => an_instance_of(Array), + "pageInfo" => anything + ) ) expect(runner_data['tagList']).to match_array runner.tag_list end + + it 'does not execute more queries per runner', :aggregate_failures do + # warm-up license cache and so on: + personal_access_token = create(:personal_access_token, user: user) + args = { current_user: user, token: { personal_access_token: personal_access_token } } + post_graphql(query, **args) + expect(graphql_data_at(:runner)).not_to be_nil + + personal_access_token = create(:personal_access_token, user: another_admin) + args = { current_user: another_admin, token: { personal_access_token: personal_access_token } } + control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) } + + create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: another_admin) + create(:ci_runner, :project, version: '14.0.1', projects: [project1], tag_list: %w[tag3 tag8], creator: another_admin) + + expect { post_graphql(query, **args) }.not_to exceed_query_limit(control) + end end shared_examples 'retrieval with no admin url' do @@ -135,7 +165,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do runner_data = graphql_data_at(:runner) expect(runner_data).not_to be_nil - expect(runner_data).to match a_graphql_entity_for(runner, admin_url: nil) + expect(runner_data).to match a_graphql_entity_for(runner, admin_url: nil, edit_admin_url: nil) expect(runner_data['tagList']).to match_array runner.tag_list end end @@ -307,6 +337,24 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do it_behaves_like 'runner details fetch' end + describe 'for registration type' do + context 'when registered with registration token' do + let(:runner) do + create(:ci_runner, registration_type: :registration_token) + end + + it_behaves_like 'runner details fetch' + end + + context 'when registered with authenticated user' do + let(:runner) do + create(:ci_runner, registration_type: :authenticated_user) + end + + it_behaves_like 'runner details fetch' + end + end + describe 'for group runner request' do let(:query) do %( @@ -568,14 +616,14 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do end end - context 'with request made by creator' do + context 'with request made by creator', :frozen_time do let(:user) { creator } context 'with runner created in UI' do let(:registration_type) { :authenticated_user } - context 'with runner created in last 3 hours' do - let(:created_at) { (3.hours - 1.second).ago } + context 'with runner created in last hour' do + let(:created_at) { (Ci::Runner::REGISTRATION_AVAILABILITY_TIME - 1.second).ago } context 'with no runner machine registed yet' do it_behaves_like 'an ephemeral_authentication_token' @@ -589,13 +637,13 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do end context 'with runner created almost too long ago' do - let(:created_at) { (3.hours - 1.second).ago } + let(:created_at) { (Ci::Runner::REGISTRATION_AVAILABILITY_TIME - 1.second).ago } it_behaves_like 'an ephemeral_authentication_token' end context 'with runner created too long ago' do - let(:created_at) { 3.hours.ago } + let(:created_at) { Ci::Runner::REGISTRATION_AVAILABILITY_TIME.ago } it_behaves_like 'a protected ephemeral_authentication_token' end @@ -604,8 +652,8 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do context 'with runner registered from command line' do let(:registration_type) { :registration_token } - context 'with runner created in last 3 hours' do - let(:created_at) { (3.hours - 1.second).ago } + context 'with runner created in last 1 hour' do + let(:created_at) { (Ci::Runner::REGISTRATION_AVAILABILITY_TIME - 1.second).ago } it_behaves_like 'a protected ephemeral_authentication_token' end @@ -628,6 +676,12 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do <<~SINGLE runner(id: "#{runner.to_global_id}") { #{all_graphql_fields_for('CiRunner', excluded: excluded_fields)} + createdBy { + id + username + webPath + webUrl + } groups { nodes { id @@ -658,7 +712,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do let(:active_group_runner2) { create(:ci_runner, :group) } # Exclude fields that are already hardcoded above - let(:excluded_fields) { %w[jobs groups projects ownerProject] } + let(:excluded_fields) { %w[createdBy jobs groups projects ownerProject] } let(:single_query) do <<~QUERY @@ -691,6 +745,8 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do control = ActiveRecord::QueryRecorder.new { post_graphql(single_query, **args) } + personal_access_token = create(:personal_access_token, user: another_admin) + args = { current_user: another_admin, token: { personal_access_token: personal_access_token } } expect { post_graphql(double_query, **args) }.not_to exceed_query_limit(control) expect(graphql_data.count).to eq 6 diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb index 75d8609dc38..c8706ae9698 100644 --- a/spec/requests/api/graphql/ci/runners_spec.rb +++ b/spec/requests/api/graphql/ci/runners_spec.rb @@ -11,16 +11,24 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do let_it_be(:instance_runner) { create(:ci_runner, :instance, version: 'abc', revision: '123', description: 'Instance runner', ip_address: '127.0.0.1') } let_it_be(:project_runner) { create(:ci_runner, :project, active: false, version: 'def', revision: '456', description: 'Project runner', projects: [project], ip_address: '127.0.0.1') } - let(:runners_graphql_data) { graphql_data['runners'] } + let(:runners_graphql_data) { graphql_data_at(:runners) } let(:params) { {} } let(:fields) do <<~QUERY nodes { - #{all_graphql_fields_for('CiRunner', excluded: %w[ownerProject])} + #{all_graphql_fields_for('CiRunner', excluded: %w[createdBy ownerProject])} + createdBy { + username + webPath + webUrl + } ownerProject { id + path + fullPath + webUrl } } QUERY @@ -50,6 +58,25 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do it 'returns expected runner' do expect(runners_graphql_data['nodes']).to contain_exactly(a_graphql_entity_for(expected_runner)) end + + it 'does not execute more queries per runner', :aggregate_failures do + # warm-up license cache and so on: + personal_access_token = create(:personal_access_token, user: current_user) + args = { current_user: current_user, token: { personal_access_token: personal_access_token } } + post_graphql(query, **args) + expect(graphql_data_at(:runners, :nodes)).not_to be_empty + + admin2 = create(:admin) + personal_access_token = create(:personal_access_token, user: admin2) + args = { current_user: admin2, token: { personal_access_token: personal_access_token } } + control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) } + + create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: admin2) + create(:ci_runner, :project, version: '14.0.1', projects: [project], tag_list: %w[tag3 tag8], + creator: current_user) + + expect { post_graphql(query, **args) }.not_to exceed_query_limit(control) + end end context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do diff --git a/spec/requests/api/graphql/current_user/todos_query_spec.rb b/spec/requests/api/graphql/current_user/todos_query_spec.rb index f7e23aeb241..ee019a99f8d 100644 --- a/spec/requests/api/graphql/current_user/todos_query_spec.rb +++ b/spec/requests/api/graphql/current_user/todos_query_spec.rb @@ -19,7 +19,7 @@ RSpec.describe 'Query current user todos', feature_category: :source_code_manage let(:fields) do <<~QUERY nodes { - #{all_graphql_fields_for('todos'.classify, max_depth: 2)} + #{all_graphql_fields_for('todos'.classify, max_depth: 2, excluded: ['productAnalyticsState'])} } QUERY end diff --git a/spec/requests/api/graphql/current_user_query_spec.rb b/spec/requests/api/graphql/current_user_query_spec.rb index 53d2580caee..aceef77920d 100644 --- a/spec/requests/api/graphql/current_user_query_spec.rb +++ b/spec/requests/api/graphql/current_user_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting project information', feature_category: :authentication_and_authorization do +RSpec.describe 'getting project information', feature_category: :system_access do include GraphqlHelpers let(:fields) do diff --git a/spec/requests/api/graphql/custom_emoji_query_spec.rb b/spec/requests/api/graphql/custom_emoji_query_spec.rb index 7b804623e01..1858ea831dd 100644 --- a/spec/requests/api/graphql/custom_emoji_query_spec.rb +++ b/spec/requests/api/graphql/custom_emoji_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting custom emoji within namespace', feature_category: :not_owned do +RSpec.describe 'getting custom emoji within namespace', feature_category: :shared do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb index 4d615d3eaa4..0a5c87ebef8 100644 --- a/spec/requests/api/graphql/multiplexed_queries_spec.rb +++ b/spec/requests/api/graphql/multiplexed_queries_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'Multiplexed queries', feature_category: :not_owned do +RSpec.describe 'Multiplexed queries', feature_category: :shared do include GraphqlHelpers it 'returns responses for multiple queries' do diff --git a/spec/requests/api/graphql/mutations/achievements/award_spec.rb b/spec/requests/api/graphql/mutations/achievements/award_spec.rb new file mode 100644 index 00000000000..9bc0751e924 --- /dev/null +++ b/spec/requests/api/graphql/mutations/achievements/award_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Achievements::Award, feature_category: :user_profile do + include GraphqlHelpers + + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + let_it_be(:recipient) { create(:user) } + + let(:mutation) { graphql_mutation(:achievements_award, params) } + let(:achievement_id) { achievement&.to_global_id } + let(:recipient_id) { recipient&.to_global_id } + let(:params) do + { + achievement_id: achievement_id, + user_id: recipient_id + } + end + + subject { post_graphql_mutation(mutation, current_user: current_user) } + + def mutation_response + graphql_mutation_response(:achievements_create) + end + + before_all do + group.add_developer(developer) + group.add_maintainer(maintainer) + end + + context 'when the user does not have permission' do + let(:current_user) { developer } + + it_behaves_like 'a mutation that returns a top-level access error' + + it 'does not create an achievement' do + expect { subject }.not_to change { Achievements::UserAchievement.count } + end + end + + context 'when the user has permission' do + let(:current_user) { maintainer } + + context 'when the params are invalid' do + let(:achievement) { nil } + + it 'returns the validation error' do + subject + + expect(graphql_errors.to_s).to include('invalid value for achievementId (Expected value to not be null)') + end + end + + context 'when the recipient_id is invalid' do + let(:recipient_id) { "gid://gitlab/User/#{non_existing_record_id}" } + + it 'returns the validation error' do + subject + + expect(graphql_data_at(:achievements_award, + :errors)).to include("Couldn't find User with 'id'=#{non_existing_record_id}") + end + end + + context 'when the achievement_id is invalid' do + let(:achievement_id) { "gid://gitlab/Achievements::Achievement/#{non_existing_record_id}" } + + it 'returns the validation error' do + subject + + expect(graphql_errors.to_s) + .to include("The resource that you are attempting to access does not exist or you don't have permission") + end + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(achievements: false) + end + + it 'returns the relevant error' do + subject + + expect(graphql_errors.to_s) + .to include("The resource that you are attempting to access does not exist or you don't have permission") + end + end + + it 'creates an achievement' do + expect { subject }.to change { Achievements::UserAchievement.count }.by(1) + end + + it 'returns the new achievement' do + subject + + expect(graphql_data_at(:achievements_award, :user_achievement, :achievement, :id)) + .to eq(achievement.to_global_id.to_s) + expect(graphql_data_at(:achievements_award, :user_achievement, :user, :id)) + .to eq(recipient.to_global_id.to_s) + end + end +end diff --git a/spec/requests/api/graphql/mutations/achievements/revoke_spec.rb b/spec/requests/api/graphql/mutations/achievements/revoke_spec.rb new file mode 100644 index 00000000000..925a1bb9fcc --- /dev/null +++ b/spec/requests/api/graphql/mutations/achievements/revoke_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Achievements::Revoke, feature_category: :user_profile do + include GraphqlHelpers + + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + let_it_be(:user_achievement) { create(:user_achievement, achievement: achievement) } + + let(:mutation) { graphql_mutation(:achievements_revoke, params) } + let(:user_achievement_id) { user_achievement&.to_global_id } + let(:params) { { user_achievement_id: user_achievement_id } } + + subject { post_graphql_mutation(mutation, current_user: current_user) } + + def mutation_response + graphql_mutation_response(:achievements_create) + end + + before_all do + group.add_developer(developer) + group.add_maintainer(maintainer) + end + + context 'when the user does not have permission' do + let(:current_user) { developer } + + it_behaves_like 'a mutation that returns a top-level access error' + + it 'does not revoke any achievements' do + expect { subject }.not_to change { Achievements::UserAchievement.where(revoked_by_user_id: nil).count } + end + end + + context 'when the user has permission' do + let(:current_user) { maintainer } + + context 'when the params are invalid' do + let(:user_achievement) { nil } + + it 'returns the validation error' do + subject + + expect(graphql_errors.to_s).to include('invalid value for userAchievementId (Expected value to not be null)') + end + end + + context 'when the user_achievement_id is invalid' do + let(:user_achievement_id) { "gid://gitlab/Achievements::UserAchievement/#{non_existing_record_id}" } + + it 'returns the validation error' do + subject + + expect(graphql_errors.to_s) + .to include("The resource that you are attempting to access does not exist or you don't have permission") + end + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(achievements: false) + end + + it 'returns the relevant error' do + subject + + expect(graphql_errors.to_s) + .to include("The resource that you are attempting to access does not exist or you don't have permission") + end + end + + it 'revokes an achievement' do + expect { subject }.to change { Achievements::UserAchievement.where(revoked_by_user_id: nil).count }.by(-1) + end + + it 'returns the revoked achievement' do + subject + + expect(graphql_data_at(:achievements_revoke, :user_achievement, :achievement, :id)) + .to eq(achievement.to_global_id.to_s) + expect(graphql_data_at(:achievements_revoke, :user_achievement, :revoked_by_user, :id)) + .to eq(current_user.to_global_id.to_s) + expect(graphql_data_at(:achievements_revoke, :user_achievement, :revoked_at)) + .not_to be_nil + end + end +end diff --git a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb index 64ea6d32f5f..b3d25155a6f 100644 --- a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb +++ b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_category: :not_owned do +RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_category: :shared do include GraphqlHelpers let_it_be(:admin) { create(:admin) } diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb index fdbff0f93cd..18cc85d36e0 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Adding an AwardEmoji', feature_category: :not_owned do +RSpec.describe 'Adding an AwardEmoji', feature_category: :shared do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb index e200bfc2d18..7ec2b061a88 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Removing an AwardEmoji', feature_category: :not_owned do +RSpec.describe 'Removing an AwardEmoji', feature_category: :shared do include GraphqlHelpers let(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb index 6dba2b58357..7c6a487cdd0 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Toggling an AwardEmoji', feature_category: :not_owned do +RSpec.describe 'Toggling an AwardEmoji', feature_category: :shared do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb b/spec/requests/api/graphql/mutations/ci/job/cancel_spec.rb index 468a9e57f56..abad1ae0812 100644 --- a/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job/cancel_spec.rb @@ -15,12 +15,12 @@ RSpec.describe "JobCancel", feature_category: :continuous_integration do id: job.to_global_id.to_s } graphql_mutation(:job_cancel, variables, - <<-QL + <<-QL errors job { id } - QL + QL ) end diff --git a/spec/requests/api/graphql/mutations/ci/job_play_spec.rb b/spec/requests/api/graphql/mutations/ci/job/play_spec.rb index 9ba80e51dee..8100274ed97 100644 --- a/spec/requests/api/graphql/mutations/ci/job_play_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job/play_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'JobPlay', feature_category: :continuous_integration do let(:mutation) do graphql_mutation(:job_play, variables, - <<-QL + <<-QL errors job { id @@ -28,7 +28,7 @@ RSpec.describe 'JobPlay', feature_category: :continuous_integration do } } } - QL + QL ) end diff --git a/spec/requests/api/graphql/mutations/ci/job_retry_spec.rb b/spec/requests/api/graphql/mutations/ci/job/retry_spec.rb index e49ee6f3163..4114c77491b 100644 --- a/spec/requests/api/graphql/mutations/ci/job_retry_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job/retry_spec.rb @@ -16,12 +16,12 @@ RSpec.describe 'JobRetry', feature_category: :continuous_integration do id: job.to_global_id.to_s } graphql_mutation(:job_retry, variables, - <<-QL + <<-QL errors job { id } - QL + QL ) end @@ -57,12 +57,12 @@ RSpec.describe 'JobRetry', feature_category: :continuous_integration do } graphql_mutation(:job_retry, variables, - <<-QL + <<-QL errors job { id } - QL + QL ) end diff --git a/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb b/spec/requests/api/graphql/mutations/ci/job/unschedule_spec.rb index 6868b0ea279..08e155e808b 100644 --- a/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job/unschedule_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'JobUnschedule', feature_category: :continuous_integration do id: job.to_global_id.to_s } graphql_mutation(:job_unschedule, variables, - <<-QL + <<-QL errors job { id diff --git a/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb new file mode 100644 index 00000000000..4e25669a0ca --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb @@ -0,0 +1,197 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'BulkDestroy', feature_category: :build_artifacts do + include GraphqlHelpers + + let(:maintainer) { create(:user) } + let(:developer) { create(:user) } + let(:first_artifact) { create(:ci_job_artifact) } + let(:second_artifact) { create(:ci_job_artifact, project: project) } + let(:second_artifact_another_project) { create(:ci_job_artifact) } + let(:project) { first_artifact.job.project } + let(:ids) { [first_artifact.to_global_id.to_s] } + let(:not_authorized_project_error_message) do + "The resource that you are attempting to access " \ + "does not exist or you don't have permission to perform this action" + end + + let(:mutation) do + variables = { + project_id: project.to_global_id.to_s, + ids: ids + } + graphql_mutation(:bulk_destroy_job_artifacts, variables, <<~FIELDS) + destroyedCount + destroyedIds + errors + FIELDS + end + + let(:mutation_response) { graphql_mutation_response(:bulk_destroy_job_artifacts) } + + it 'fails to destroy the artifact if a user not in a project' do + post_graphql_mutation(mutation, current_user: maintainer) + + expect(graphql_errors).to include( + a_hash_including('message' => not_authorized_project_error_message) + ) + + expect(first_artifact.reload).to be_persisted + end + + context 'when the `ci_job_artifact_bulk_destroy` feature flag is disabled' do + before do + stub_feature_flags(ci_job_artifact_bulk_destroy: false) + project.add_maintainer(maintainer) + end + + it 'returns a resource not available error' do + post_graphql_mutation(mutation, current_user: maintainer) + + expect(graphql_errors).to contain_exactly( + hash_including( + 'message' => '`ci_job_artifact_bulk_destroy` feature flag is disabled.' + ) + ) + end + end + + context "when the user is a developer in a project" do + before do + project.add_developer(developer) + end + + it 'fails to destroy the artifact' do + post_graphql_mutation(mutation, current_user: developer) + + expect(graphql_errors).to include( + a_hash_including('message' => not_authorized_project_error_message) + ) + + expect(response).to have_gitlab_http_status(:success) + expect(first_artifact.reload).to be_persisted + end + end + + context "when the user is a maintainer in a project" do + before do + project.add_maintainer(maintainer) + end + + shared_examples 'failing mutation' do + it 'rejects the request' do + post_graphql_mutation(mutation, current_user: maintainer) + + expect(graphql_errors(mutation_response)).to include(expected_error_message) + + expected_not_found_artifacts.each do |artifact| + expect { artifact.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + expected_found_artifacts.each do |artifact| + expect(artifact.reload).to be_persisted + end + end + end + + it 'destroys the artifact' do + post_graphql_mutation(mutation, current_user: maintainer) + + expect(mutation_response).to include("destroyedCount" => 1, "destroyedIds" => [gid_string(first_artifact)]) + expect(response).to have_gitlab_http_status(:success) + expect { first_artifact.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + context "and one artifact doesn't belong to the project" do + let(:not_owned_artifact) { create(:ci_job_artifact) } + let(:ids) { [first_artifact.to_global_id.to_s, not_owned_artifact.to_global_id.to_s] } + let(:expected_error_message) { "Not all artifacts belong to requested project" } + let(:expected_not_found_artifacts) { [] } + let(:expected_found_artifacts) { [first_artifact, not_owned_artifact] } + + it_behaves_like 'failing mutation' + end + + context "and multiple artifacts belong to the maintainer's project" do + let(:ids) { [first_artifact.to_global_id.to_s, second_artifact.to_global_id.to_s] } + + it 'destroys all artifacts' do + post_graphql_mutation(mutation, current_user: maintainer) + + expect(mutation_response).to include( + "destroyedCount" => 2, + "destroyedIds" => [gid_string(first_artifact), gid_string(second_artifact)] + ) + + expect(response).to have_gitlab_http_status(:success) + expect { first_artifact.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect { second_artifact.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context "and one artifact belongs to a different maintainer's project" do + let(:ids) { [first_artifact.to_global_id.to_s, second_artifact_another_project.to_global_id.to_s] } + let(:expected_found_artifacts) { [first_artifact, second_artifact_another_project] } + let(:expected_not_found_artifacts) { [] } + let(:expected_error_message) { "Not all artifacts belong to requested project" } + + it_behaves_like 'failing mutation' + end + + context "and not found" do + let(:ids) { [first_artifact.to_global_id.to_s, second_artifact.to_global_id.to_s] } + let(:not_found_ids) { expected_not_found_artifacts.map(&:id).join(',') } + let(:expected_error_message) { "Artifacts (#{not_found_ids}) not found" } + + before do + expected_not_found_artifacts.each(&:destroy!) + end + + context "with one artifact" do + let(:expected_not_found_artifacts) { [second_artifact] } + let(:expected_found_artifacts) { [first_artifact] } + + it_behaves_like 'failing mutation' + end + + context "with all artifact" do + let(:expected_not_found_artifacts) { [first_artifact, second_artifact] } + let(:expected_found_artifacts) { [] } + + it_behaves_like 'failing mutation' + end + end + + context 'when empty request' do + before do + project.add_maintainer(maintainer) + end + + context 'with nil value' do + let(:ids) { nil } + + it 'does nothing and returns empty answer' do + post_graphql_mutation(mutation, current_user: maintainer) + + expect_graphql_errors_to_include(/was provided invalid value for ids \(Expected value to not be null\)/) + end + end + + context 'with empty array' do + let(:ids) { [] } + + it 'raises argument error' do + post_graphql_mutation(mutation, current_user: maintainer) + + expect_graphql_errors_to_include(/IDs array of job artifacts can not be empty/) + end + end + end + + def gid_string(object) + Gitlab::GlobalId.build(object, id: object.id).to_s + end + end +end diff --git a/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb b/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb index 99e55c44773..0951d165d46 100644 --- a/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb @@ -101,21 +101,6 @@ RSpec.describe 'ProjectCiCdSettingsUpdate', feature_category: :continuous_integr expect(response).to have_gitlab_http_status(:success) expect(project.ci_inbound_job_token_scope_enabled).to eq(true) end - - context 'when ci_inbound_job_token_scope disabled' do - before do - stub_feature_flags(ci_inbound_job_token_scope: false) - end - - it 'does not update inbound_job_token_scope_enabled' do - post_graphql_mutation(mutation, current_user: user) - - project.reload - - expect(response).to have_gitlab_http_status(:success) - expect(project.ci_inbound_job_token_scope_enabled).to eq(true) - end - end end it 'updates ci_opt_in_jwt' do diff --git a/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb b/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb new file mode 100644 index 00000000000..f39f6f84c99 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'RunnerCreate', feature_category: :runner_fleet do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:admin) { create(:admin) } + + let(:mutation_params) do + { + description: 'create description', + maintenance_note: 'create maintenance note', + maximum_timeout: 900, + access_level: 'REF_PROTECTED', + paused: true, + run_untagged: false, + tag_list: %w[tag1 tag2] + } + end + + let(:mutation) do + variables = { + **mutation_params + } + + graphql_mutation( + :runner_create, + variables, + <<-QL + runner { + ephemeralAuthenticationToken + + runnerType + description + maintenanceNote + paused + tagList + accessLevel + locked + maximumTimeout + runUntagged + } + errors + QL + ) + end + + let(:mutation_response) { graphql_mutation_response(:runner_create) } + + context 'when user does not have permissions' do + let(:current_user) { user } + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['errors']).to contain_exactly "Insufficient permissions" + end + end + + context 'when user has permissions', :enable_admin_mode do + let(:current_user) { admin } + + context 'when :create_runner_workflow_for_admin feature flag is disabled' do + before do + stub_feature_flags(create_runner_workflow_for_admin: false) + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_errors).not_to be_empty + expect(graphql_errors[0]['message']) + .to eq("`create_runner_workflow_for_admin` feature flag is disabled.") + end + end + + context 'when success' do + it do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + + mutation_params.each_key do |key| + expect(mutation_response['runner'][key.to_s.camelize(:lower)]).to eq mutation_params[key] + end + + expect(mutation_response['runner']['ephemeralAuthenticationToken']).to start_with 'glrt' + + expect(mutation_response['errors']).to eq([]) + end + end + + context 'when failure' do + let(:mutation_params) do + { + description: "", + maintenanceNote: "", + paused: true, + accessLevel: "NOT_PROTECTED", + runUntagged: false, + tagList: + [], + maximumTimeout: 1 + } + end + + it do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + + expect(mutation_response['errors']).to contain_exactly( + "Tags list can not be empty when runner is not allowed to pick untagged jobs", + "Maximum timeout needs to be at least 10 minutes" + ) + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb b/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb index ea2ce8a13e2..19a52086f34 100644 --- a/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb +++ b/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creation of a new Custom Emoji', feature_category: :not_owned do +RSpec.describe 'Creation of a new Custom Emoji', feature_category: :shared do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb b/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb index ad7a043909a..2623d3d8410 100644 --- a/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Deletion of custom emoji', feature_category: :not_owned do +RSpec.describe 'Deletion of custom emoji', feature_category: :shared do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/mutations/design_management/update_spec.rb b/spec/requests/api/graphql/mutations/design_management/update_spec.rb new file mode 100644 index 00000000000..9558f2538f1 --- /dev/null +++ b/spec/requests/api/graphql/mutations/design_management/update_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "updating designs", feature_category: :design_management do + include GraphqlHelpers + include DesignManagementTestHelpers + + let_it_be(:issue) { create(:issue) } + let_it_be_with_reload(:design) { create(:design, description: 'old description', issue: issue) } + let_it_be(:developer) { create(:user, developer_projects: [issue.project]) } + + let(:user) { developer } + let(:description) { 'new description' } + + let(:mutation) do + input = { + id: design.to_global_id.to_s, + description: description + }.compact + + graphql_mutation(:design_management_update, input, <<~FIELDS) + errors + design { + description + descriptionHtml + } + FIELDS + end + + let(:update_design) { post_graphql_mutation(mutation, current_user: user) } + let(:mutation_response) { graphql_mutation_response(:design_management_update) } + + before do + enable_design_management + end + + it 'updates design' do + update_design + + expect(graphql_errors).not_to be_present + expect(mutation_response).to eq( + 'errors' => [], + 'design' => { + 'description' => description, + 'descriptionHtml' => "<p data-sourcepos=\"1:1-1:15\" dir=\"auto\">#{description}</p>" + } + ) + end + + context 'when the user is not allowed to update designs' do + let(:user) { create(:user) } + + it 'returns an error' do + update_design + + expect(graphql_errors).to be_present + end + end + + context 'when update fails' do + let(:description) { 'x' * 1_000_001 } + + it 'returns an error' do + update_design + + expect(graphql_errors).not_to be_present + expect(mutation_response).to eq( + 'errors' => ["Description is too long (maximum is 1000000 characters)"], + 'design' => { + 'description' => 'old description', + 'descriptionHtml' => '<p data-sourcepos="1:1-1:15" dir="auto">old description</p>' + } + ) + end + end +end diff --git a/spec/requests/api/graphql/mutations/issues/bulk_update_spec.rb b/spec/requests/api/graphql/mutations/issues/bulk_update_spec.rb index b9c83311908..b729585a89b 100644 --- a/spec/requests/api/graphql/mutations/issues/bulk_update_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/bulk_update_spec.rb @@ -8,7 +8,9 @@ RSpec.describe 'Bulk update issues', feature_category: :team_planning do let_it_be(:developer) { create(:user) } let_it_be(:group) { create(:group).tap { |group| group.add_developer(developer) } } let_it_be(:project) { create(:project, group: group) } - let_it_be(:updatable_issues, reload: true) { create_list(:issue, 2, project: project) } + let_it_be(:label1) { create(:group_label, group: group) } + let_it_be(:label2) { create(:group_label, group: group) } + let_it_be(:updatable_issues, reload: true) { create_list(:issue, 2, project: project, label_ids: [label1.id]) } let_it_be(:milestone) { create(:milestone, group: group) } let(:parent) { project } @@ -21,10 +23,36 @@ RSpec.describe 'Bulk update issues', feature_category: :team_planning do let(:additional_arguments) do { assignee_ids: [current_user.to_gid.to_s], - milestone_id: milestone.to_gid.to_s + milestone_id: milestone.to_gid.to_s, + state_event: :CLOSE, + add_label_ids: [label2.to_gid.to_s], + remove_label_ids: [label1.to_gid.to_s], + subscription_event: :UNSUBSCRIBE } end + before_all do + updatable_issues.each { |i| i.subscribe(developer, project) } + end + + context 'when Gitlab is FOSS only' do + unless Gitlab.ee? + context 'when parent is a group' do + let(:parent) { group } + + it 'does not allow bulk updating issues at the group level' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_errors).to contain_exactly( + hash_including( + 'message' => match(/does not represent an instance of IssueParent/) + ) + ) + end + end + end + end + context 'when the `bulk_update_issues_mutation` feature flag is disabled' do before do stub_feature_flags(bulk_update_issues_mutation: false) @@ -67,6 +95,11 @@ RSpec.describe 'Bulk update issues', feature_category: :team_planning do updatable_issues.each(&:reload) end.to change { updatable_issues.flat_map(&:assignee_ids) }.from([]).to([current_user.id] * 2) .and(change { updatable_issues.map(&:milestone_id) }.from([nil] * 2).to([milestone.id] * 2)) + .and(change { updatable_issues.map(&:state) }.from(['opened'] * 2).to(['closed'] * 2)) + .and(change { updatable_issues.flat_map(&:label_ids) }.from([label1.id] * 2).to([label2.id] * 2)) + .and( + change { updatable_issues.map { |i| i.subscribed?(developer, project) } }.from([true] * 2).to([false] * 2) + ) expect(mutation_response).to include( 'updatedIssueCount' => updatable_issues.count @@ -88,37 +121,6 @@ RSpec.describe 'Bulk update issues', feature_category: :team_planning do end end - context 'when scoping to a parent group' do - let(:parent) { group } - - it 'updates all issues' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - updatable_issues.each(&:reload) - end.to change { updatable_issues.flat_map(&:assignee_ids) }.from([]).to([current_user.id] * 2) - .and(change { updatable_issues.map(&:milestone_id) }.from([nil] * 2).to([milestone.id] * 2)) - - expect(mutation_response).to include( - 'updatedIssueCount' => updatable_issues.count - ) - end - - context 'when current user cannot read the specified group' do - let(:parent) { create(:group, :private) } - - it 'returns a resource not found error' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(graphql_errors).to contain_exactly( - hash_including( - 'message' => "The resource that you are attempting to access does not exist or you don't have " \ - 'permission to perform this action' - ) - ) - end - end - end - context 'when setting arguments to null or none' do let(:additional_arguments) { { assignee_ids: [], milestone_id: nil } } diff --git a/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb b/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb index ad70129a7bc..f15b52f53a3 100644 --- a/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb +++ b/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb @@ -5,126 +5,14 @@ require 'spec_helper' RSpec.describe 'GroupMemberBulkUpdate', feature_category: :subgroups do include GraphqlHelpers - let_it_be(:current_user) { create(:user) } - let_it_be(:user1) { create(:user) } - let_it_be(:user2) { create(:user) } - let_it_be(:group) { create(:group) } - let_it_be(:group_member1) { create(:group_member, group: group, user: user1) } - let_it_be(:group_member2) { create(:group_member, group: group, user: user2) } + let_it_be(:parent_group) { create(:group) } + let_it_be(:parent_group_member) { create(:group_member, group: parent_group) } + let_it_be(:group) { create(:group, parent: parent_group) } + let_it_be(:source) { group } + let_it_be(:member_type) { :group_member } let_it_be(:mutation_name) { :group_member_bulk_update } + let_it_be(:source_id_key) { 'group_id' } + let_it_be(:response_member_field) { 'groupMembers' } - let(:input) do - { - 'group_id' => group.to_global_id.to_s, - 'user_ids' => [user1.to_global_id.to_s, user2.to_global_id.to_s], - 'access_level' => 'GUEST' - } - end - - let(:extra_params) { { expires_at: 10.days.from_now } } - let(:input_params) { input.merge(extra_params) } - let(:mutation) { graphql_mutation(mutation_name, input_params) } - let(:mutation_response) { graphql_mutation_response(mutation_name) } - - context 'when user is not logged-in' do - it_behaves_like 'a mutation that returns a top-level access error' - end - - context 'when user is not an owner' do - before do - group.add_maintainer(current_user) - end - - it_behaves_like 'a mutation that returns a top-level access error' - end - - context 'when user is an owner' do - before do - group.add_owner(current_user) - end - - shared_examples 'updates the user access role' do - specify do - post_graphql_mutation(mutation, current_user: current_user) - - new_access_levels = mutation_response['groupMembers'].map { |member| member['accessLevel']['integerValue'] } - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['errors']).to be_empty - expect(new_access_levels).to all(be Gitlab::Access::GUEST) - end - end - - it_behaves_like 'updates the user access role' - - context 'when inherited members are passed' do - let_it_be(:subgroup) { create(:group, parent: group) } - let_it_be(:subgroup_member) { create(:group_member, group: subgroup) } - - let(:input) do - { - 'group_id' => group.to_global_id.to_s, - 'user_ids' => [user1.to_global_id.to_s, user2.to_global_id.to_s, subgroup_member.user.to_global_id.to_s], - 'access_level' => 'GUEST' - } - end - - it 'does not update the members' do - post_graphql_mutation(mutation, current_user: current_user) - - error = Mutations::Members::Groups::BulkUpdate::INVALID_MEMBERS_ERROR - expect(json_response['errors'].first['message']).to include(error) - end - end - - context 'when members count is more than the allowed limit' do - let(:max_members_update_limit) { 1 } - - before do - stub_const('Mutations::Members::Groups::BulkUpdate::MAX_MEMBERS_UPDATE_LIMIT', max_members_update_limit) - end - - it 'does not update the members' do - post_graphql_mutation(mutation, current_user: current_user) - - error = Mutations::Members::Groups::BulkUpdate::MAX_MEMBERS_UPDATE_ERROR - expect(json_response['errors'].first['message']).to include(error) - end - end - - context 'when the update service raises access denied error' do - before do - allow_next_instance_of(Members::UpdateService) do |instance| - allow(instance).to receive(:execute).and_raise(Gitlab::Access::AccessDeniedError) - end - end - - it 'does not update the members' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(mutation_response['groupMembers']).to be_nil - expect(mutation_response['errors']) - .to contain_exactly("Unable to update members, please check user permissions.") - end - end - - context 'when the update service returns an error message' do - before do - allow_next_instance_of(Members::UpdateService) do |instance| - error_result = { - message: 'Expires at cannot be a date in the past', - status: :error, - members: [group_member1] - } - allow(instance).to receive(:execute).and_return(error_result) - end - end - - it 'will pass through the error' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(mutation_response['groupMembers'].first['id']).to eq(group_member1.to_global_id.to_s) - expect(mutation_response['errors']).to contain_exactly('Expires at cannot be a date in the past') - end - end - end + it_behaves_like 'members bulk update mutation' end diff --git a/spec/requests/api/graphql/mutations/members/projects/bulk_update_spec.rb b/spec/requests/api/graphql/mutations/members/projects/bulk_update_spec.rb new file mode 100644 index 00000000000..cbef9715cbe --- /dev/null +++ b/spec/requests/api/graphql/mutations/members/projects/bulk_update_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'ProjectMemberBulkUpdate', feature_category: :projects do + include GraphqlHelpers + + let_it_be(:parent_group) { create(:group) } + let_it_be(:parent_group_member) { create(:group_member, group: parent_group) } + let_it_be(:project) { create(:project, group: parent_group) } + let_it_be(:source) { project } + let_it_be(:member_type) { :project_member } + let_it_be(:mutation_name) { :project_member_bulk_update } + let_it_be(:source_id_key) { 'project_id' } + let_it_be(:response_member_field) { 'projectMembers' } + + it_behaves_like 'members bulk update mutation' +end diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb index bce57b47aab..3c7f4a030f9 100644 --- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb +++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb @@ -19,7 +19,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create, feature_categ graphql_mutation_response(:create_annotation) end - specify { expect(described_class).to require_graphql_authorizations(:create_metrics_dashboard_annotation) } + specify { expect(described_class).to require_graphql_authorizations(:admin_metrics_dashboard_annotation) } context 'when annotation source is environment' do let(:mutation) do diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb index f505dc25dc0..c104138b725 100644 --- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete, feature_categ graphql_mutation_response(:delete_annotation) end - specify { expect(described_class).to require_graphql_authorizations(:delete_metrics_dashboard_annotation) } + specify { expect(described_class).to require_graphql_authorizations(:admin_metrics_dashboard_annotation) } context 'when the user has permission to delete the annotation' do before do diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb index a6253ba424b..2a0b5f291dc 100644 --- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb @@ -104,7 +104,8 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do end context 'as work item' do - let(:noteable) { create(:work_item, :issue, project: project) } + let_it_be(:project) { create(:project) } + let_it_be(:noteable) { create(:work_item, :issue, project: project) } context 'when using internal param' do let(:variables_extra) { { internal: true } } @@ -130,6 +131,19 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do it_behaves_like 'a mutation that returns top-level errors', errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] end + + context 'when body contains quick actions' do + let_it_be(:noteable) { create(:work_item, :task, project: project) } + + let(:variables_extra) { {} } + + it_behaves_like 'work item supports labels widget updates via quick actions' + it_behaves_like 'work item does not support labels widget updates via quick actions' + it_behaves_like 'work item supports assignee widget updates via quick actions' + it_behaves_like 'work item does not support assignee widget updates via quick actions' + it_behaves_like 'work item supports start and due date widget updates via quick actions' + it_behaves_like 'work item does not support start and due date widget updates via quick actions' + end end end diff --git a/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb b/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb new file mode 100644 index 00000000000..a77c026dd06 --- /dev/null +++ b/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Sync project fork", feature_category: :source_code_management do + include GraphqlHelpers + include ProjectForksHelper + include ExclusiveLeaseHelpers + + let_it_be(:source_project) { create(:project, :repository, :public) } + let_it_be(:current_user) { create(:user, maintainer_projects: [source_project]) } + let_it_be(:project, refind: true) { fork_project(source_project, current_user, { repository: true }) } + let_it_be(:target_branch) { project.default_branch } + + let(:mutation) do + params = { project_path: project.full_path, target_branch: target_branch } + + graphql_mutation(:project_sync_fork, params) do + <<-QL.strip_heredoc + details { + ahead + behind + isSyncing + hasConflicts + } + errors + QL + end + end + + before do + source_project.change_head('feature') + end + + context 'when synchronize_fork feature flag is disabled' do + before do + stub_feature_flags(synchronize_fork: false) + end + + it 'does not call the sync service' do + expect(::Projects::Forks::SyncWorker).not_to receive(:perform_async) + + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_mutation_response(:project_sync_fork)).to eq( + { + 'details' => nil, + 'errors' => ['Feature flag is disabled'] + }) + end + end + + context 'when the user does not have permission' do + let_it_be(:current_user) { create(:user) } + + it_behaves_like 'a mutation that returns a top-level access error' + + it 'does not call the sync service' do + expect(::Projects::Forks::SyncWorker).not_to receive(:perform_async) + + post_graphql_mutation(mutation, current_user: current_user) + end + end + + context 'when the user has permission' do + context 'and the sync service executes successfully', :sidekiq_inline do + it 'calls the sync service' do + expect(::Projects::Forks::SyncWorker).to receive(:perform_async).and_call_original + + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_mutation_response(:project_sync_fork)).to eq( + { + 'details' => { 'ahead' => 30, 'behind' => 0, "hasConflicts" => false, "isSyncing" => false }, + 'errors' => [] + }) + end + end + + context 'and the sync service fails to execute' do + let(:target_branch) { 'markdown' } + + def expect_error_response(message) + expect(::Projects::Forks::SyncWorker).not_to receive(:perform_async) + + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_mutation_response(:project_sync_fork)['errors']).to eq([message]) + end + + context 'when fork details cannot be resolved' do + let_it_be(:project) { source_project } + + it 'returns an error' do + expect_error_response('This branch of this project cannot be updated from the upstream') + end + end + + context 'when the previous execution resulted in a conflict' do + it 'returns an error' do + expect_next_instance_of(::Projects::Forks::Details) do |instance| + expect(instance).to receive(:has_conflicts?).twice.and_return(true) + end + + expect_error_response('The synchronization cannot happen due to the merge conflict') + expect(graphql_mutation_response(:project_sync_fork)['details']['hasConflicts']).to eq(true) + end + end + + context 'when the request is rate limited' do + it 'returns an error' do + expect(Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true) + + expect_error_response('This service has been called too many times.') + end + end + + context 'when another fork sync is in progress' do + it 'returns an error' do + expect_next_instance_of(Projects::Forks::Details) do |instance| + lease = instance_double(Gitlab::ExclusiveLease, try_obtain: false, exists?: true) + expect(instance).to receive(:exclusive_lease).twice.and_return(lease) + end + + expect_error_response('Another fork sync is already in progress') + expect(graphql_mutation_response(:project_sync_fork)['details']['isSyncing']).to eq(true) + end + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb index fa087e6773c..3b98ee3c2e9 100644 --- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb @@ -193,7 +193,6 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d end it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:user) { current_user } let(:property) { 'g_edit_by_snippet_ide' } let(:namespace) { project.namespace } @@ -203,8 +202,6 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d let(:context) do [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context] end - - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } end end end diff --git a/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb index 97bf060356a..d2df3e8bda2 100644 --- a/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb @@ -23,7 +23,7 @@ RSpec.describe "Create a work item from a task in a work item's description", fe } end - let(:mutation) { graphql_mutation(:workItemCreateFromTask, input) } + let(:mutation) { graphql_mutation(:workItemCreateFromTask, input, nil, ['productAnalyticsState']) } let(:mutation_response) { graphql_mutation_response(:work_item_create_from_task) } context 'the user is not allowed to update a work item' do diff --git a/spec/requests/api/graphql/mutations/work_items/create_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_spec.rb index 16f78b67b5c..7519389ab49 100644 --- a/spec/requests/api/graphql/mutations/work_items/create_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/create_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do 'title' => 'new title', 'description' => 'new description', 'confidential' => true, - 'workItemTypeId' => WorkItems::Type.default_by_type(:task).to_global_id.to_s + 'workItemTypeId' => WorkItems::Type.default_by_type(:task).to_gid.to_s } end @@ -43,14 +43,14 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do expect(created_work_item.work_item_type.base_type).to eq('task') expect(mutation_response['workItem']).to include( input.except('workItemTypeId').merge( - 'id' => created_work_item.to_global_id.to_s, + 'id' => created_work_item.to_gid.to_s, 'workItemType' => hash_including('name' => 'Task') ) ) end context 'when input is invalid' do - let(:input) { { 'title' => '', 'workItemTypeId' => WorkItems::Type.default_by_type(:task).to_global_id.to_s } } + let(:input) { { 'title' => '', 'workItemTypeId' => WorkItems::Type.default_by_type(:task).to_gid.to_s } } it 'does not create and returns validation errors' do expect do @@ -98,8 +98,8 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do let(:input) do { title: 'item1', - workItemTypeId: WorkItems::Type.default_by_type(:task).to_global_id.to_s, - hierarchyWidget: { 'parentId' => parent.to_global_id.to_s } + workItemTypeId: WorkItems::Type.default_by_type(:task).to_gid.to_s, + hierarchyWidget: { 'parentId' => parent.to_gid.to_s } } end @@ -110,7 +110,7 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do expect(widgets_response).to include( { 'children' => { 'edges' => [] }, - 'parent' => { 'id' => parent.to_global_id.to_s }, + 'parent' => { 'id' => parent.to_gid.to_s }, 'type' => 'HIERARCHY' } ) @@ -137,6 +137,40 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do expect(graphql_errors.first['message']).to include('No object found for `parentId') end end + + context 'when adjacent is already in place' do + let_it_be(:adjacent) { create(:work_item, :task, project: project) } + + let(:work_item) { WorkItem.last } + + let(:input) do + { + title: 'item1', + workItemTypeId: WorkItems::Type.default_by_type(:task).to_gid.to_s, + hierarchyWidget: { 'parentId' => parent.to_gid.to_s } + } + end + + before(:all) do + create(:parent_link, work_item_parent: parent, work_item: adjacent, relative_position: 0) + end + + it 'creates work item and sets the relative position to be AFTER adjacent' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change(WorkItem, :count).by(1) + + expect(response).to have_gitlab_http_status(:success) + expect(widgets_response).to include( + { + 'children' => { 'edges' => [] }, + 'parent' => { 'id' => parent.to_gid.to_s }, + 'type' => 'HIERARCHY' + } + ) + expect(work_item.parent_link.relative_position).to be > adjacent.parent_link.relative_position + end + end end context 'when unsupported widget input is sent' do @@ -144,7 +178,7 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do { 'title' => 'new title', 'description' => 'new description', - 'workItemTypeId' => WorkItems::Type.default_by_type(:test_case).to_global_id.to_s, + 'workItemTypeId' => WorkItems::Type.default_by_type(:test_case).to_gid.to_s, 'hierarchyWidget' => {} } end @@ -181,8 +215,8 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do let(:input) do { title: 'some WI', - workItemTypeId: WorkItems::Type.default_by_type(:task).to_global_id.to_s, - milestoneWidget: { 'milestoneId' => milestone.to_global_id.to_s } + workItemTypeId: WorkItems::Type.default_by_type(:task).to_gid.to_s, + milestoneWidget: { 'milestoneId' => milestone.to_gid.to_s } } end @@ -196,7 +230,7 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do expect(widgets_response).to include( { 'type' => 'MILESTONE', - 'milestone' => { 'id' => milestone.to_global_id.to_s } + 'milestone' => { 'id' => milestone.to_gid.to_s } } ) end diff --git a/spec/requests/api/graphql/mutations/work_items/export_spec.rb b/spec/requests/api/graphql/mutations/work_items/export_spec.rb new file mode 100644 index 00000000000..3cadaab5201 --- /dev/null +++ b/spec/requests/api/graphql/mutations/work_items/export_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Export work items', feature_category: :team_planning do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } } + let_it_be(:guest) { create(:user).tap { |user| project.add_guest(user) } } + let_it_be(:work_item) { create(:work_item, project: project) } + + let(:input) { { 'projectPath' => project.full_path } } + let(:mutation) { graphql_mutation(:workItemExport, input) } + let(:mutation_response) { graphql_mutation_response(:work_item_export) } + + context 'when user is not allowed to export work items' do + let(:current_user) { guest } + + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when import_export_work_items_csv feature flag is disabled' do + let(:current_user) { reporter } + + before do + stub_feature_flags(import_export_work_items_csv: false) + end + + it_behaves_like 'a mutation that returns top-level errors', + errors: ['`import_export_work_items_csv` feature flag is disabled.'] + end + + context 'when user has permissions to export work items' do + let(:current_user) { reporter } + let(:input) do + super().merge( + 'selectedFields' => %w[TITLE AUTHOR TYPE AUTHOR_USERNAME CREATED_AT], + 'authorUsername' => 'admin', + 'iids' => [work_item.iid.to_s], + 'state' => 'opened', + 'types' => 'TASK', + 'search' => 'any', + 'in' => 'TITLE' + ) + end + + it 'schedules export job with given arguments', :aggregate_failures do + expected_arguments = { + selected_fields: ['title', 'author', 'type', 'author username', 'created_at'], + author_username: 'admin', + iids: [work_item.iid.to_s], + state: 'opened', + issue_types: ['task'], + search: 'any', + in: ['title'] + } + + expect(IssuableExportCsvWorker) + .to receive(:perform_async).with(:work_item, current_user.id, project.id, expected_arguments) + + post_graphql_mutation(mutation, current_user: current_user) + + expect(mutation_response['errors']).to be_empty + end + end +end diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb index ddd294e8f82..76dc60be799 100644 --- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb @@ -7,10 +7,11 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } + let_it_be(:author) { create(:user).tap { |user| project.add_reporter(user) } } let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } } let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } } let_it_be(:guest) { create(:user).tap { |user| project.add_guest(user) } } - let_it_be(:work_item, refind: true) { create(:work_item, project: project) } + let_it_be(:work_item, refind: true) { create(:work_item, project: project, author: author) } let(:work_item_event) { 'CLOSE' } let(:input) { { 'stateEvent' => work_item_event, 'title' => 'updated title' } } @@ -843,6 +844,140 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do end end + context 'when updating notifications subscription' do + let_it_be(:current_user) { reporter } + let(:input) { { 'notificationsWidget' => { 'subscribed' => desired_state } } } + + let(:fields) do + <<~FIELDS + workItem { + widgets { + type + ... on WorkItemWidgetNotifications { + subscribed + } + } + } + errors + FIELDS + end + + subject(:update_work_item) { post_graphql_mutation(mutation, current_user: current_user) } + + shared_examples 'subscription updated successfully' do + let_it_be(:subscription) do + create( + :subscription, project: project, + user: current_user, + subscribable: work_item, + subscribed: !desired_state + ) + end + + it "updates existing work item's subscription state" do + expect do + update_work_item + subscription.reload + end.to change(subscription, :subscribed).to(desired_state) + .and(change { work_item.reload.subscribed?(reporter, project) }.to(desired_state)) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['workItem']['widgets']).to include( + { + 'subscribed' => desired_state, + 'type' => 'NOTIFICATIONS' + } + ) + end + end + + shared_examples 'subscription update ignored' do + context 'when user is subscribed with a subscription record' do + let_it_be(:subscription) do + create( + :subscription, project: project, + user: current_user, + subscribable: work_item, + subscribed: !desired_state + ) + end + + it 'ignores the update request' do + expect do + update_work_item + subscription.reload + end.to not_change(subscription, :subscribed) + .and(not_change { work_item.subscribed?(current_user, project) }) + + expect(response).to have_gitlab_http_status(:success) + end + end + + context 'when user is subscribed by being a participant' do + let_it_be(:current_user) { author } + + it 'ignores the update request' do + expect do + update_work_item + end.to not_change(Subscription, :count) + .and(not_change { work_item.subscribed?(current_user, project) }) + + expect(response).to have_gitlab_http_status(:success) + end + end + end + + context 'when work item update fails' do + let_it_be(:desired_state) { false } + let(:input) { { 'title' => nil, 'notificationsWidget' => { 'subscribed' => desired_state } } } + + it_behaves_like 'subscription update ignored' + end + + context 'when user cannot update work item' do + let_it_be(:desired_state) { false } + + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(current_user, :update_subscription, work_item).and_return(false) + end + + it_behaves_like 'subscription update ignored' + end + + context 'when user can update work item' do + context 'when subscribing to notifications' do + let_it_be(:desired_state) { true } + + it_behaves_like 'subscription updated successfully' + end + + context 'when unsubscribing from notifications' do + let_it_be(:desired_state) { false } + + it_behaves_like 'subscription updated successfully' + + context 'when user is subscribed by being a participant' do + let_it_be(:current_user) { author } + + it 'creates a subscription with desired state' do + expect { update_work_item }.to change(Subscription, :count).by(1) + .and(change { work_item.reload.subscribed?(author, project) }.to(desired_state)) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['workItem']['widgets']).to include( + { + 'subscribed' => desired_state, + 'type' => 'NOTIFICATIONS' + } + ) + end + end + end + end + end + context 'when unsupported widget input is sent' do let_it_be(:test_case) { create(:work_item_type, :default, :test_case) } let_it_be(:work_item) { create(:work_item, work_item_type: test_case, project: project) } diff --git a/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb index 999c685ac6a..717de983871 100644 --- a/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'Update a work item task', feature_category: :team_planning do let(:task_params) { { 'title' => 'UPDATED' } } let(:task_input) { { 'id' => task.to_global_id.to_s }.merge(task_params) } let(:input) { { 'id' => work_item.to_global_id.to_s, 'taskData' => task_input } } - let(:mutation) { graphql_mutation(:workItemUpdateTask, input) } + let(:mutation) { graphql_mutation(:workItemUpdateTask, input, nil, ['productAnalyticsState']) } let(:mutation_response) { graphql_mutation_response(:work_item_update_task) } context 'the user is not allowed to read a work item' do diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb index 4e12da3e3ab..83edacaf831 100644 --- a/spec/requests/api/graphql/namespace/projects_spec.rb +++ b/spec/requests/api/graphql/namespace/projects_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'getting projects', feature_category: :projects do projects(includeSubgroups: #{include_subgroups}) { edges { node { - #{all_graphql_fields_for('Project', max_depth: 1)} + #{all_graphql_fields_for('Project', max_depth: 1, excluded: ['productAnalyticsState'])} } } } diff --git a/spec/requests/api/graphql/packages/package_spec.rb b/spec/requests/api/graphql/packages/package_spec.rb index 82fcc5254ad..7610a4aaac1 100644 --- a/spec/requests/api/graphql/packages/package_spec.rb +++ b/spec/requests/api/graphql/packages/package_spec.rb @@ -270,6 +270,31 @@ RSpec.describe 'package details', feature_category: :package_registry do it 'returns composer_config_repository_url correctly' do expect(graphql_data_at(:package, :composer_config_repository_url)).to eq("localhost/#{group.id}") end + + context 'with access to package registry for everyone' do + before do + project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) + subject + end + + it 'returns pypi_url correctly' do + expect(graphql_data_at(:package, :pypi_url)).to eq("http://__token__:<your_personal_token>@localhost/api/v4/projects/#{project.id}/packages/pypi/simple") + end + end + + context 'when project is public' do + let_it_be(:public_project) { create(:project, :public, group: group) } + let_it_be(:composer_package) { create(:composer_package, project: public_project) } + let(:package_global_id) { global_id_of(composer_package) } + + before do + subject + end + + it 'returns pypi_url correctly' do + expect(graphql_data_at(:package, :pypi_url)).to eq("http://localhost/api/v4/projects/#{public_project.id}/packages/pypi/simple") + end + end end context 'web_path' do diff --git a/spec/requests/api/graphql/project/base_service_spec.rb b/spec/requests/api/graphql/project/base_service_spec.rb index 7b1b95eaf58..b27cddea07b 100644 --- a/spec/requests/api/graphql/project/base_service_spec.rb +++ b/spec/requests/api/graphql/project/base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'query Jira service', feature_category: :authentication_and_authorization do +RSpec.describe 'query Jira service', feature_category: :system_access do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb index 7ccf8a6f5bf..9a40a972256 100644 --- a/spec/requests/api/graphql/project/container_repositories_spec.rb +++ b/spec/requests/api/graphql/project/container_repositories_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'getting container repositories in a project', feature_category: let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten } let_it_be(:container_expiration_policy) { project.container_expiration_policy } - let(:excluded_fields) { %w[pipeline jobs] } + let(:excluded_fields) { %w[pipeline jobs productAnalyticsState] } let(:container_repositories_fields) do <<~GQL edges { @@ -155,7 +155,7 @@ RSpec.describe 'getting container repositories in a project', feature_category: it_behaves_like 'handling graphql network errors with the container registry' it_behaves_like 'not hitting graphql network errors with the container registry' do - let(:excluded_fields) { %w[pipeline jobs tags tagsCount] } + let(:excluded_fields) { %w[pipeline jobs tags tagsCount productAnalyticsState] } end it 'returns the total count of container repositories' do diff --git a/spec/requests/api/graphql/project/flow_metrics_spec.rb b/spec/requests/api/graphql/project/flow_metrics_spec.rb new file mode 100644 index 00000000000..3b5758b3a2e --- /dev/null +++ b/spec/requests/api/graphql/project/flow_metrics_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting project flow metrics', feature_category: :value_stream_management do + include GraphqlHelpers + + let_it_be(:group) { create(:group) } + let_it_be(:project1) { create(:project, :repository, group: group) } + # This is done so we can use the same count expectations in the shared examples and + # reuse the shared example for the group-level test. + let_it_be(:project2) { project1 } + let_it_be(:production_environment1) { create(:environment, :production, project: project1) } + let_it_be(:production_environment2) { production_environment1 } + let_it_be(:current_user) { create(:user, maintainer_projects: [project1]) } + + let(:full_path) { project1.full_path } + let(:context) { :project } + + it_behaves_like 'value stream analytics flow metrics issueCount examples' + + it_behaves_like 'value stream analytics flow metrics deploymentCount examples' +end diff --git a/spec/requests/api/graphql/project/fork_details_spec.rb b/spec/requests/api/graphql/project/fork_details_spec.rb index efd48b00833..0baf29b970e 100644 --- a/spec/requests/api/graphql/project/fork_details_spec.rb +++ b/spec/requests/api/graphql/project/fork_details_spec.rb @@ -10,12 +10,13 @@ RSpec.describe 'getting project fork details', feature_category: :source_code_ma let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } let_it_be(:forked_project) { fork_project(project, current_user, repository: true) } + let(:ref) { 'feature' } let(:queried_project) { forked_project } let(:query) do graphql_query_for(:project, { full_path: queried_project.full_path }, <<~QUERY - forkDetails(ref: "feature"){ + forkDetails(ref: "#{ref}"){ ahead behind } @@ -41,6 +42,38 @@ RSpec.describe 'getting project fork details', feature_category: :source_code_ma end end + context 'when project source is not visible' do + it 'does not return fork details' do + project.team.truncate + + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['forkDetails']).to be_nil + end + end + + context 'when the specified ref does not exist' do + let(:ref) { 'non-existent-branch' } + + it 'does not return fork details' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['forkDetails']).to be_nil + end + end + + context 'when fork_divergence_counts feature flag is disabled' do + before do + stub_feature_flags(fork_divergence_counts: false) + end + + it 'does not return fork details' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['forkDetails']).to be_nil + end + end + context 'when a user cannot read the code' do let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index 8407faa967e..156886ca211 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -588,8 +588,9 @@ RSpec.describe 'getting merge request listings nested in a project', feature_cat end let(:query) do + # Adding a no-op `not` filter to mimic the same query as the frontend does graphql_query_for(:project, { full_path: project.full_path }, <<~QUERY) - mergeRequests(mergedAfter: "2020-01-01", mergedBefore: "2020-01-05", first: 0) { + mergeRequests(mergedAfter: "2020-01-01", mergedBefore: "2020-01-05", first: 0, not: { labels: null }) { totalTimeToMerge count } diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb index f49165a88ea..d5dd12de63e 100644 --- a/spec/requests/api/graphql/project/work_items_spec.rb +++ b/spec/requests/api/graphql/project/work_items_spec.rb @@ -313,6 +313,34 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team end end + context 'when fetching work item notifications widget' do + let(:fields) do + <<~GRAPHQL + nodes { + widgets { + type + ... on WorkItemWidgetNotifications { + subscribed + } + } + } + GRAPHQL + end + + it 'executes limited number of N+1 queries', :use_sql_query_cache do + control = ActiveRecord::QueryRecorder.new(skip_cached: false) do + post_graphql(query, current_user: current_user) + end + + create_list(:work_item, 3, project: project) + + # Performs 1 extra query per item to fetch subscriptions + expect { post_graphql(query, current_user: current_user) } + .not_to exceed_all_query_limit(control).with_threshold(3) + expect_graphql_errors_to_be_empty + end + end + def item_ids graphql_dig_at(items_data, :node, :id) end diff --git a/spec/requests/api/graphql/query_spec.rb b/spec/requests/api/graphql/query_spec.rb index 2b9d66ec744..d93077b1c70 100644 --- a/spec/requests/api/graphql/query_spec.rb +++ b/spec/requests/api/graphql/query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query', feature_category: :not_owned do +RSpec.describe 'Query', feature_category: :shared do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/user/user_achievements_query_spec.rb b/spec/requests/api/graphql/user/user_achievements_query_spec.rb new file mode 100644 index 00000000000..bf9b2b429cc --- /dev/null +++ b/spec/requests/api/graphql/user/user_achievements_query_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'UserAchievements', feature_category: :user_profile do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :private) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + let_it_be(:user_achievements) { create_list(:user_achievement, 2, achievement: achievement, user: user) } + let_it_be(:fields) do + <<~HEREDOC + userAchievements { + nodes { + id + achievement { + id + } + user { + id + } + awardedByUser { + id + } + revokedByUser { + id + } + } + } + HEREDOC + end + + let_it_be(:query) do + graphql_query_for('user', { id: user.to_global_id.to_s }, fields) + end + + let(:current_user) { user } + + before_all do + group.add_guest(user) + end + + before do + post_graphql(query, current_user: current_user) + end + + it_behaves_like 'a working graphql query' + + it 'returns all user_achievements' do + expect(graphql_data_at(:user, :userAchievements, :nodes)).to contain_exactly( + a_graphql_entity_for(user_achievements[0]), + a_graphql_entity_for(user_achievements[1]) + ) + end + + it 'can lookahead to eliminate N+1 queries', :use_clean_rails_memory_store_caching do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + post_graphql(query, current_user: user) + end.count + + achievement2 = create(:achievement, namespace: group) + create_list(:user_achievement, 2, achievement: achievement2, user: user) + + expect { post_graphql(query, current_user: user) }.not_to exceed_all_query_limit(control_count) + end + + context 'when the achievements feature flag is disabled for a namespace' do + let_it_be(:group2) { create(:group) } + let_it_be(:achievement2) { create(:achievement, namespace: group2) } + let_it_be(:user_achievement2) { create(:user_achievement, achievement: achievement2, user: user) } + + before do + stub_feature_flags(achievements: false) + stub_feature_flags(achievements: group2) + post_graphql(query, current_user: current_user) + end + + it 'does not return user_achievements for that namespace' do + expect(graphql_data_at(:user, :userAchievements, :nodes)).to contain_exactly( + a_graphql_entity_for(user_achievement2) + ) + end + end + + context 'when current user is not a member of the private group' do + let(:current_user) { create(:user) } + + specify { expect(graphql_data_at(:user, :userAchievements, :nodes)).to be_empty } + end +end diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb index 0fad4f4ff3a..24c72a8bb00 100644 --- a/spec/requests/api/graphql/work_item_spec.rb +++ b/spec/requests/api/graphql/work_item_spec.rb @@ -373,6 +373,32 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do ) end end + + describe 'notifications widget' do + let(:work_item_fields) do + <<~GRAPHQL + id + widgets { + type + ... on WorkItemWidgetNotifications { + subscribed + } + } + GRAPHQL + end + + it 'returns widget information' do + expect(work_item_data).to include( + 'id' => work_item.to_gid.to_s, + 'widgets' => include( + hash_including( + 'type' => 'NOTIFICATIONS', + 'subscribed' => work_item.subscribed?(current_user, project) + ) + ) + ) + end + end end context 'when an Issue Global ID is provided' do diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index d7724371cce..8a3c5261eb6 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'GraphQL', feature_category: :not_owned do +RSpec.describe 'GraphQL', feature_category: :shared do include GraphqlHelpers include AfterNextHelpers diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb index 91f64d02d43..2f05b0fcf21 100644 --- a/spec/requests/api/group_milestones_spec.rb +++ b/spec/requests/api/group_milestones_spec.rb @@ -4,35 +4,41 @@ require 'spec_helper' RSpec.describe API::GroupMilestones, feature_category: :team_planning do let_it_be(:user) { create(:user) } - let_it_be(:group) { create(:group, :private) } + let_it_be_with_refind(:group) { create(:group, :private) } let_it_be(:project) { create(:project, namespace: group) } let_it_be(:group_member) { create(:group_member, group: group, user: user) } - let_it_be(:closed_milestone) { create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone') } - let_it_be(:milestone) { create(:milestone, group: group, title: 'version2', description: 'open milestone') } + let_it_be(:closed_milestone) do + create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone') + end + + let_it_be_with_reload(:milestone) do + create(:milestone, group: group, title: 'version2', description: 'open milestone', updated_at: 4.days.ago) + end let(:route) { "/groups/#{group.id}/milestones" } + shared_examples 'listing all milestones' do + it 'returns correct list of milestones' do + get api(route, user), params: params + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.size).to eq(milestones.size) + expect(json_response.map { |entry| entry["id"] }).to eq(milestones.map(&:id)) + end + end + it_behaves_like 'group and project milestones', "/groups/:id/milestones" describe 'GET /groups/:id/milestones' do - context 'when include_parent_milestones is true' do - let_it_be(:ancestor_group) { create(:group, :private) } - let_it_be(:ancestor_group_milestone) { create(:milestone, group: ancestor_group) } - let_it_be(:params) { { include_parent_milestones: true } } - - before_all do - group.update!(parent: ancestor_group) - end + let_it_be(:ancestor_group) { create(:group, :private) } + let_it_be(:ancestor_group_milestone) { create(:milestone, group: ancestor_group, updated_at: 2.days.ago) } - shared_examples 'listing all milestones' do - it 'returns correct list of milestones' do - get api(route, user), params: params + before_all do + group.update!(parent: ancestor_group) + end - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.size).to eq(milestones.size) - expect(json_response.map { |entry| entry["id"] }).to eq(milestones.map(&:id)) - end - end + context 'when include_parent_milestones is true' do + let(:params) { { include_parent_milestones: true } } context 'when user has access to ancestor groups' do let(:milestones) { [ancestor_group_milestone, milestone, closed_milestone] } @@ -45,10 +51,26 @@ RSpec.describe API::GroupMilestones, feature_category: :team_planning do it_behaves_like 'listing all milestones' context 'when iids param is present' do - let_it_be(:params) { { include_parent_milestones: true, iids: [milestone.iid] } } + let(:params) { { include_parent_milestones: true, iids: [milestone.iid] } } it_behaves_like 'listing all milestones' end + + context 'when updated_before param is present' do + let(:params) { { updated_before: 1.day.ago.iso8601, include_parent_milestones: true } } + + it_behaves_like 'listing all milestones' do + let(:milestones) { [ancestor_group_milestone, milestone] } + end + end + + context 'when updated_after param is present' do + let(:params) { { updated_after: 1.day.ago.iso8601, include_parent_milestones: true } } + + it_behaves_like 'listing all milestones' do + let(:milestones) { [closed_milestone] } + end + end end context 'when user has no access to ancestor groups' do @@ -63,6 +85,22 @@ RSpec.describe API::GroupMilestones, feature_category: :team_planning do end end end + + context 'when updated_before param is present' do + let(:params) { { updated_before: 1.day.ago.iso8601 } } + + it_behaves_like 'listing all milestones' do + let(:milestones) { [milestone] } + end + end + + context 'when updated_after param is present' do + let(:params) { { updated_after: 1.day.ago.iso8601 } } + + it_behaves_like 'listing all milestones' do + let(:milestones) { [closed_milestone] } + end + end end describe 'GET /groups/:id/milestones/:milestone_id/issues' do diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb index e3d538d72ba..ff20e7ea9dd 100644 --- a/spec/requests/api/group_variables_spec.rb +++ b/spec/requests/api/group_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupVariables, feature_category: :pipeline_authoring do +RSpec.describe API::GroupVariables, feature_category: :pipeline_composition do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } let_it_be(:variable) { create(:ci_group_variable, group: group) } diff --git a/spec/requests/api/helm_packages_spec.rb b/spec/requests/api/helm_packages_spec.rb index 584f6e3c7d4..d6afd6f86ff 100644 --- a/spec/requests/api/helm_packages_spec.rb +++ b/spec/requests/api/helm_packages_spec.rb @@ -17,7 +17,15 @@ RSpec.describe API::HelmPackages, feature_category: :package_registry do let_it_be(:package_file2_2) { create(:helm_package_file, package: package2, file_sha256: 'file2', file_name: 'filename2.tgz', channel: 'test', description: 'hello from test channel') } let_it_be(:other_package) { create(:npm_package, project: project) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_helm_user' } } + let(:snowplow_gitlab_standard_context) { snowplow_context } + + def snowplow_context(user_role: :developer) + if user_role == :anonymous + { project: project, namespace: project.namespace, property: 'i_package_helm_user' } + else + { project: project, namespace: project.namespace, property: 'i_package_helm_user', user: user } + end + end describe 'GET /api/v4/projects/:id/packages/helm/:channel/index.yaml' do let(:project_id) { project.id } @@ -65,6 +73,7 @@ RSpec.describe API::HelmPackages, feature_category: :package_registry do with_them do let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) } + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } before do project.update!(visibility: visibility.to_s) @@ -75,6 +84,8 @@ RSpec.describe API::HelmPackages, feature_category: :package_registry do end context 'with access to package registry for everyone' do + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: :anonymous) } + before do project.update!(visibility: Gitlab::VisibilityLevel::PRIVATE) project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) @@ -116,6 +127,7 @@ RSpec.describe API::HelmPackages, feature_category: :package_registry do with_them do let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) } let(:headers) { user_headers.merge(workhorse_headers) } + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } before do project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) @@ -178,6 +190,7 @@ RSpec.describe API::HelmPackages, feature_category: :package_registry do with_them do let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) } let(:headers) { user_headers.merge(workhorse_headers) } + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } before do project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 38275ce0057..0be9df41e8f 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' require 'raven/transports/dummy' require_relative '../../../config/initializers/sentry' -RSpec.describe API::Helpers, :enable_admin_mode, feature_category: :authentication_and_authorization do +RSpec.describe API::Helpers, :enable_admin_mode, feature_category: :system_access do include API::APIGuard::HelperMethods include described_class include TermsHelper diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index ca32271f573..dff41c4c477 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::Base, feature_category: :authentication_and_authorization do +RSpec.describe API::Internal::Base, feature_category: :system_access do include GitlabShellHelpers include APIInternalBaseHelpers @@ -514,7 +514,7 @@ RSpec.describe API::Internal::Base, feature_category: :authentication_and_author expect(json_response["gl_repository"]).to eq("wiki-#{project.id}") expect(json_response["gl_key_type"]).to eq("key") expect(json_response["gl_key_id"]).to eq(key.id) - expect(user.reload.last_activity_on).to be_nil + expect(user.reload.last_activity_on).to eql(Date.today) end it_behaves_like 'sets hook env' do @@ -553,7 +553,7 @@ RSpec.describe API::Internal::Base, feature_category: :authentication_and_author expect(json_response["status"]).to be_truthy expect(json_response["gl_project_path"]).to eq(personal_snippet.repository.full_path) expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}") - expect(user.reload.last_activity_on).to be_nil + expect(user.reload.last_activity_on).to eql(Date.today) end it_behaves_like 'sets hook env' do @@ -585,7 +585,7 @@ RSpec.describe API::Internal::Base, feature_category: :authentication_and_author expect(json_response["status"]).to be_truthy expect(json_response["gl_project_path"]).to eq(project_snippet.repository.full_path) expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}") - expect(user.reload.last_activity_on).to be_nil + expect(user.reload.last_activity_on).to eql(Date.today) end it_behaves_like 'sets hook env' do @@ -703,7 +703,7 @@ RSpec.describe API::Internal::Base, feature_category: :authentication_and_author expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) - expect(user.reload.last_activity_on).to be_nil + expect(user.reload.last_activity_on).to eql(Date.today) end it_behaves_like 'rate limited request' do @@ -862,7 +862,7 @@ RSpec.describe API::Internal::Base, feature_category: :authentication_and_author expect(json_response['status']).to be_truthy expect(json_response['payload']).to eql(payload) expect(json_response['gl_console_messages']).to eql(console_messages) - expect(user.reload.last_activity_on).to be_nil + expect(user.reload.last_activity_on).to eql(Date.today) end end end diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index be76e55269a..547b9071f94 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -306,4 +306,151 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_manageme end end end + + describe 'POST /internal/kubernetes/authorize_proxy_user', :clean_gitlab_redis_sessions do + include SessionHelpers + + def send_request(headers: {}, params: {}) + post api('/internal/kubernetes/authorize_proxy_user'), params: params, headers: headers.reverse_merge(jwt_auth_headers) + end + + def stub_user_session(user, csrf_token) + stub_session( + { + 'warden.user.user.key' => [[user.id], user.authenticatable_salt], + '_csrf_token' => csrf_token + } + ) + end + + def stub_user_session_with_no_user_id(user, csrf_token) + stub_session( + { + 'warden.user.user.key' => [[nil], user.authenticatable_salt], + '_csrf_token' => csrf_token + } + ) + end + + def mask_token(encoded_token) + controller = ActionController::Base.new + raw_token = controller.send(:decode_csrf_token, encoded_token) + controller.send(:mask_token, raw_token) + end + + def new_token + ActionController::Base.new.send(:generate_csrf_token) + end + + let_it_be(:project) { create(:project) } + let_it_be(:group) { create(:group) } + let_it_be(:user_access_config) do + { + 'user_access' => { + 'access_as' => { 'agent' => {} }, + 'projects' => [{ 'id' => project.full_path }], + 'groups' => [{ 'id' => group.full_path }] + } + } + end + + let_it_be(:configuration_project) do + create( + :project, :custom_repo, + files: { + ".gitlab/agents/the-agent/config.yaml" => user_access_config.to_yaml + } + ) + end + + let_it_be(:agent) { create(:cluster_agent, name: 'the-agent', project: configuration_project) } + let_it_be(:another_agent) { create(:cluster_agent) } + + let(:user) { create(:user) } + + before do + allow(::Gitlab::Kas).to receive(:enabled?).and_return true + end + + it 'returns 400 when cookie is invalid' do + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: '123', csrf_token: mask_token(new_token) }) + + expect(response).to have_gitlab_http_status(:bad_request) + end + + it 'returns 401 when session is not found' do + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id('abc') + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(new_token) }) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'returns 401 when CSRF token does not match' do + public_id = stub_user_session(user, new_token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(new_token) }) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'returns 404 for non-existent agent' do + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: non_existing_record_id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'returns 403 when user has no access' do + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'returns 200 when user has access' do + project.add_member(user, :developer) + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:success) + end + + it 'returns 401 when user has valid KAS cookie and CSRF token but has no access to requested agent' do + project.add_member(user, :developer) + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: another_agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'returns 401 when global flag is disabled' do + stub_feature_flags(kas_user_access: false) + + project.add_member(user, :developer) + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'returns 401 when user id is not found in session' do + project.add_member(user, :developer) + token = new_token + public_id = stub_user_session_with_no_user_id(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end end diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb index 56f1089843b..20fb9100ebb 100644 --- a/spec/requests/api/internal/pages_spec.rb +++ b/spec/requests/api/internal/pages_spec.rb @@ -3,193 +3,97 @@ require 'spec_helper' RSpec.describe API::Internal::Pages, feature_category: :pages do - let(:auth_headers) do - jwt_token = JWT.encode({ 'iss' => 'gitlab-pages' }, Gitlab::Pages.secret, 'HS256') - { Gitlab::Pages::INTERNAL_API_REQUEST_HEADER => jwt_token } + let_it_be(:group) { create(:group, name: 'mygroup') } + let_it_be_with_reload(:project) { create(:project, name: 'myproject', group: group) } + + let(:auth_header) do + { + Gitlab::Pages::INTERNAL_API_REQUEST_HEADER => JWT.encode( + { 'iss' => 'gitlab-pages' }, + Gitlab::Pages.secret, 'HS256') + } end - let(:pages_secret) { SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH) } - before do - allow(Gitlab::Pages).to receive(:secret).and_return(pages_secret) + allow(Gitlab::Pages) + .to receive(:secret) + .and_return(SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH)) + stub_pages_object_storage(::Pages::DeploymentUploader) end - describe "GET /internal/pages/status" do - def query_enabled(headers = {}) - get api("/internal/pages/status"), headers: headers - end - + describe 'GET /internal/pages/status' do it 'responds with 401 Unauthorized' do - query_enabled + get api('/internal/pages/status') expect(response).to have_gitlab_http_status(:unauthorized) end it 'responds with 204 no content' do - query_enabled(auth_headers) + get api('/internal/pages/status'), headers: auth_header expect(response).to have_gitlab_http_status(:no_content) expect(response.body).to be_empty end end - describe "GET /internal/pages" do - def query_host(host, headers = {}) - get api("/internal/pages"), headers: headers, params: { host: host } - end - - around do |example| - freeze_time do - example.run - end - end - - context 'not authenticated' do + describe 'GET /internal/pages' do + context 'when not authenticated' do it 'responds with 401 Unauthorized' do - query_host('pages.gitlab.io') + get api('/internal/pages') expect(response).to have_gitlab_http_status(:unauthorized) end end - context 'authenticated' do - def query_host(host) - jwt_token = JWT.encode({ 'iss' => 'gitlab-pages' }, Gitlab::Pages.secret, 'HS256') - headers = { Gitlab::Pages::INTERNAL_API_REQUEST_HEADER => jwt_token } - - super(host, headers) + context 'when authenticated' do + before do + project.update_pages_deployment!(create(:pages_deployment, project: project)) end - def deploy_pages(project) - deployment = create(:pages_deployment, project: project) - project.mark_pages_as_deployed - project.update_pages_deployment!(deployment) + around do |example| + freeze_time do + example.run + end end - context 'domain does not exist' do + context 'when domain does not exist' do it 'responds with 204 no content' do - query_host('pages.gitlab.io') + get api('/internal/pages'), headers: auth_header, params: { host: 'any-domain.gitlab.io' } expect(response).to have_gitlab_http_status(:no_content) expect(response.body).to be_empty end end - context 'serverless domain' do - let(:namespace) { create(:namespace, name: 'gitlab-org') } - let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') } - let(:environment) { create(:environment, project: project) } - let(:pages_domain) { create(:pages_domain, domain: 'serverless.gitlab.io') } - let(:knative_without_ingress) { create(:clusters_applications_knative) } - let(:knative_with_ingress) { create(:clusters_applications_knative, external_ip: '10.0.0.1') } - - context 'without a knative ingress gateway IP' do - let!(:serverless_domain_cluster) do - create( - :serverless_domain_cluster, - uuid: 'abcdef12345678', - pages_domain: pages_domain, - knative: knative_without_ingress - ) - end - - let(:serverless_domain) do - create( - :serverless_domain, - serverless_domain_cluster: serverless_domain_cluster, - environment: environment - ) - end - - it 'responds with 204 no content' do - query_host(serverless_domain.uri.host) - - expect(response).to have_gitlab_http_status(:no_content) - expect(response.body).to be_empty - end - end - - context 'with a knative ingress gateway IP' do - let!(:serverless_domain_cluster) do - create( - :serverless_domain_cluster, - uuid: 'abcdef12345678', - pages_domain: pages_domain, - knative: knative_with_ingress - ) - end - - let(:serverless_domain) do - create( - :serverless_domain, - serverless_domain_cluster: serverless_domain_cluster, - environment: environment - ) - end - - it 'responds with 204 because of feature deprecation' do - query_host(serverless_domain.uri.host) + context 'when querying a custom domain' do + let_it_be(:pages_domain) { create(:pages_domain, domain: 'pages.io', project: project) } - expect(response).to have_gitlab_http_status(:no_content) - expect(response.body).to be_empty - - ## - # Serverless serving and reverse proxy to Kubernetes / Knative has - # been deprecated and disabled, as per - # https://gitlab.com/gitlab-org/gitlab-pages/-/issues/467 - # - # expect(response).to match_response_schema('internal/serverless/virtual_domain') - # expect(json_response['certificate']).to eq(pages_domain.certificate) - # expect(json_response['key']).to eq(pages_domain.key) - # - # expect(json_response['lookup_paths']).to eq( - # [ - # { - # 'source' => { - # 'type' => 'serverless', - # 'service' => "test-function.#{project.name}-#{project.id}-#{environment.slug}.#{serverless_domain_cluster.knative.hostname}", - # 'cluster' => { - # 'hostname' => serverless_domain_cluster.knative.hostname, - # 'address' => serverless_domain_cluster.knative.external_ip, - # 'port' => 443, - # 'cert' => serverless_domain_cluster.certificate, - # 'key' => serverless_domain_cluster.key - # } - # } - # } - # ] - # ) + context 'when there are no pages deployed for the related project' do + before do + project.mark_pages_as_not_deployed end - end - end - context 'custom domain' do - let(:namespace) { create(:namespace, name: 'gitlab-org') } - let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') } - let!(:pages_domain) { create(:pages_domain, domain: 'pages.io', project: project) } - - context 'when there are no pages deployed for the related project' do it 'responds with 204 No Content' do - query_host('pages.io') + get api('/internal/pages'), headers: auth_header, params: { host: 'pages.io' } expect(response).to have_gitlab_http_status(:no_content) end end context 'when there are pages deployed for the related project' do - it 'domain lookup is case insensitive' do - deploy_pages(project) + before do + project.mark_pages_as_deployed + end - query_host('Pages.IO') + it 'domain lookup is case insensitive' do + get api('/internal/pages'), headers: auth_header, params: { host: 'Pages.IO' } expect(response).to have_gitlab_http_status(:ok) end it 'responds with the correct domain configuration' do - deploy_pages(project) - - query_host('pages.io') + get api('/internal/pages'), headers: auth_header, params: { host: 'pages.io' } expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('internal/pages/virtual_domain') @@ -212,7 +116,8 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do 'sha256' => deployment.file_sha256, 'file_size' => deployment.size, 'file_count' => deployment.file_count - } + }, + 'unique_domain' => nil } ] ) @@ -220,20 +125,67 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do end end - context 'namespaced domain' do - let(:group) { create(:group, name: 'mygroup') } + context 'when querying a unique domain' do + before_all do + project.project_setting.update!( + pages_unique_domain: 'unique-domain', + pages_unique_domain_enabled: true + ) + end - before do - allow(Settings.pages).to receive(:host).and_return('gitlab-pages.io') - allow(Gitlab.config.pages).to receive(:url).and_return("http://gitlab-pages.io") + context 'when there are no pages deployed for the related project' do + before do + project.mark_pages_as_not_deployed + end + + it 'responds with 204 No Content' do + get api('/internal/pages'), headers: auth_header, params: { host: 'unique-domain.example.com' } + + expect(response).to have_gitlab_http_status(:no_content) + end end - context 'regular project' do - it 'responds with the correct domain configuration' do - project = create(:project, group: group, name: 'myproject') - deploy_pages(project) + context 'when there are pages deployed for the related project' do + before do + project.mark_pages_as_deployed + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(pages_unique_domain: false) + end - query_host('mygroup.gitlab-pages.io') + context 'when there are no pages deployed for the related project' do + it 'responds with 204 No Content' do + get api('/internal/pages'), headers: auth_header, params: { host: 'unique-domain.example.com' } + + expect(response).to have_gitlab_http_status(:no_content) + end + end + end + + context 'when the unique domain is disabled' do + before do + project.project_setting.update!(pages_unique_domain_enabled: false) + end + + context 'when there are no pages deployed for the related project' do + it 'responds with 204 No Content' do + get api('/internal/pages'), headers: auth_header, params: { host: 'unique-domain.example.com' } + + expect(response).to have_gitlab_http_status(:no_content) + end + end + end + + it 'domain lookup is case insensitive' do + get api('/internal/pages'), headers: auth_header, params: { host: 'Unique-Domain.example.com' } + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'responds with the correct domain configuration' do + get api('/internal/pages'), headers: auth_header, params: { host: 'unique-domain.example.com' } expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('internal/pages/virtual_domain') @@ -245,7 +197,7 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do 'project_id' => project.id, 'access_control' => false, 'https_only' => false, - 'prefix' => '/myproject/', + 'prefix' => '/', 'source' => { 'type' => 'zip', 'path' => deployment.file.url(expire_at: 1.day.from_now), @@ -253,56 +205,116 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do 'sha256' => deployment.file_sha256, 'file_size' => deployment.size, 'file_count' => deployment.file_count - } + }, + 'unique_domain' => 'unique-domain' } ] ) end end + end - it 'avoids N+1 queries' do - project = create(:project, group: group) - deploy_pages(project) - - control = ActiveRecord::QueryRecorder.new { query_host('mygroup.gitlab-pages.io') } + context 'when querying a namespaced domain' do + before do + allow(Settings.pages).to receive(:host).and_return('gitlab-pages.io') + allow(Gitlab.config.pages).to receive(:url).and_return("http://gitlab-pages.io") + end - 3.times do - project = create(:project, group: group) - deploy_pages(project) + context 'when there are no pages deployed for the related project' do + before do + project.mark_pages_as_not_deployed end - expect { query_host('mygroup.gitlab-pages.io') }.not_to exceed_query_limit(control) + it 'responds with 204 No Content' do + get api('/internal/pages'), headers: auth_header, params: { host: 'mygroup.gitlab-pages.io' } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('internal/pages/virtual_domain') + expect(json_response['lookup_paths']).to eq([]) + end end - context 'group root project' do - it 'responds with the correct domain configuration' do - project = create(:project, group: group, name: 'mygroup.gitlab-pages.io') - deploy_pages(project) + context 'when there are pages deployed for the related project' do + before do + project.mark_pages_as_deployed + end - query_host('mygroup.gitlab-pages.io') + context 'with a regular project' do + it 'responds with the correct domain configuration' do + get api('/internal/pages'), headers: auth_header, params: { host: 'mygroup.gitlab-pages.io' } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('internal/pages/virtual_domain') + + deployment = project.pages_metadatum.pages_deployment + expect(json_response['lookup_paths']).to eq( + [ + { + 'project_id' => project.id, + 'access_control' => false, + 'https_only' => false, + 'prefix' => '/myproject/', + 'source' => { + 'type' => 'zip', + 'path' => deployment.file.url(expire_at: 1.day.from_now), + 'global_id' => "gid://gitlab/PagesDeployment/#{deployment.id}", + 'sha256' => deployment.file_sha256, + 'file_size' => deployment.size, + 'file_count' => deployment.file_count + }, + 'unique_domain' => nil + } + ] + ) + end + end - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('internal/pages/virtual_domain') + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new do + get api('/internal/pages'), headers: auth_header, params: { host: 'mygroup.gitlab-pages.io' } + end - deployment = project.pages_metadatum.pages_deployment - expect(json_response['lookup_paths']).to eq( - [ - { - 'project_id' => project.id, - 'access_control' => false, - 'https_only' => false, - 'prefix' => '/', - 'source' => { - 'type' => 'zip', - 'path' => deployment.file.url(expire_at: 1.day.from_now), - 'global_id' => "gid://gitlab/PagesDeployment/#{deployment.id}", - 'sha256' => deployment.file_sha256, - 'file_size' => deployment.size, - 'file_count' => deployment.file_count + 3.times do + project = create(:project, group: group) + project.mark_pages_as_deployed + end + + expect { get api('/internal/pages'), headers: auth_header, params: { host: 'mygroup.gitlab-pages.io' } } + .not_to exceed_query_limit(control) + end + + context 'with a group root project' do + before do + project.update!(path: 'mygroup.gitlab-pages.io') + end + + it 'responds with the correct domain configuration' do + get api('/internal/pages'), headers: auth_header, params: { host: 'mygroup.gitlab-pages.io' } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('internal/pages/virtual_domain') + + deployment = project.pages_metadatum.pages_deployment + expect(json_response['lookup_paths']).to eq( + [ + { + 'project_id' => project.id, + 'access_control' => false, + 'https_only' => false, + 'prefix' => '/', + 'source' => { + 'type' => 'zip', + 'path' => deployment.file.url(expire_at: 1.day.from_now), + 'global_id' => "gid://gitlab/PagesDeployment/#{deployment.id}", + 'sha256' => deployment.file_sha256, + 'file_size' => deployment.size, + 'file_count' => deployment.file_count + }, + 'unique_domain' => nil } - } - ] - ) + ] + ) + end end end end diff --git a/spec/requests/api/internal/workhorse_spec.rb b/spec/requests/api/internal/workhorse_spec.rb index 99d0ecabbb7..2657abffae6 100644 --- a/spec/requests/api/internal/workhorse_spec.rb +++ b/spec/requests/api/internal/workhorse_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::Workhorse, :allow_forgery_protection, feature_category: :not_owned do +RSpec.describe API::Internal::Workhorse, :allow_forgery_protection, feature_category: :shared do include WorkhorseHelpers context '/authorize_upload' do diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb index 0641c2135c1..eaa3c46d0ca 100644 --- a/spec/requests/api/issues/get_group_issues_spec.rb +++ b/spec/requests/api/issues/get_group_issues_spec.rb @@ -74,7 +74,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do let(:base_url) { "/groups/#{group.id}/issues" } shared_examples 'group issues statistics' do - it 'returns issues statistics' do + it 'returns issues statistics', :aggregate_failures do get api("/groups/#{group.id}/issues_statistics", user), params: params expect(response).to have_gitlab_http_status(:ok) @@ -346,7 +346,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do group_project.add_reporter(user) end - it 'exposes known attributes' do + it 'exposes known attributes', :aggregate_failures do get api(base_url, admin) expect(response).to have_gitlab_http_status(:ok) @@ -355,7 +355,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end it 'returns all group issues (including opened and closed)' do - get api(base_url, admin) + get api(base_url, admin, admin_mode: true) expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) end @@ -385,7 +385,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end it 'returns group confidential issues for admin' do - get api(base_url, admin), params: { state: :opened } + get api(base_url, admin, admin_mode: true), params: { state: :opened } expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) end @@ -403,7 +403,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'labels parameter' do - it 'returns an array of labeled group issues' do + it 'returns an array of labeled group issues', :aggregate_failures do get api(base_url, user), params: { labels: group_label.title } expect_paginated_array_response(group_issue.id) @@ -486,7 +486,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'returns an array of issues found by iids' do + it 'returns an array of issues found by iids', :aggregate_failures do get api(base_url, user), params: { iids: [group_issue.iid] } expect_paginated_array_response(group_issue.id) @@ -505,14 +505,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect_paginated_array_response([]) end - it 'returns an array of group issues with any label' do + it 'returns an array of group issues with any label', :aggregate_failures do get api(base_url, user), params: { labels: IssuableFinder::Params::FILTER_ANY } expect_paginated_array_response(group_issue.id) expect(json_response.first['id']).to eq(group_issue.id) end - it 'returns an array of group issues with any label with labels param as array' do + it 'returns an array of group issues with any label with labels param as array', :aggregate_failures do get api(base_url, user), params: { labels: [IssuableFinder::Params::FILTER_ANY] } expect_paginated_array_response(group_issue.id) @@ -555,7 +555,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect_paginated_array_response(group_closed_issue.id) end - it 'returns an array of issues with no milestone' do + it 'returns an array of issues with no milestone', :aggregate_failures do get api(base_url, user), params: { milestone: no_milestone_title } expect(response).to have_gitlab_http_status(:ok) @@ -688,28 +688,28 @@ RSpec.describe API::Issues, feature_category: :team_planning do let!(:issue2) { create(:issue, author: user2, project: group_project, created_at: 2.days.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: group_project, created_at: 1.day.ago) } - it 'returns issues with by assignee_username' do + it 'returns issues with by assignee_username', :aggregate_failures do get api(base_url, user), params: { assignee_username: [assignee.username], scope: 'all' } expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect_paginated_array_response([issue3.id, group_confidential_issue.id]) end - it 'returns issues by assignee_username as string' do + it 'returns issues by assignee_username as string', :aggregate_failures do get api(base_url, user), params: { assignee_username: assignee.username, scope: 'all' } expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect_paginated_array_response([issue3.id, group_confidential_issue.id]) end - it 'returns error when multiple assignees are passed' do + it 'returns error when multiple assignees are passed', :aggregate_failures do get api(base_url, user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response["error"]).to include("allows one value, but found 2") end - it 'returns error when assignee_username and assignee_id are passed together' do + it 'returns error when assignee_username and assignee_id are passed together', :aggregate_failures do get api(base_url, user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' } expect(response).to have_gitlab_http_status(:bad_request) @@ -719,7 +719,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end describe "#to_reference" do - it 'exposes reference path in context of group' do + it 'exposes reference path in context of group', :aggregate_failures do get api(base_url, user) expect(json_response.first['references']['short']).to eq("##{group_closed_issue.iid}") @@ -735,7 +735,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do group_closed_issue.reload end - it 'exposes reference path in context of parent group' do + it 'exposes reference path in context of parent group', :aggregate_failures do get api("/groups/#{parent_group.id}/issues") expect(json_response.first['references']['short']).to eq("##{group_closed_issue.iid}") diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb index 6fc3903103b..915b8fff75e 100644 --- a/spec/requests/api/issues/get_project_issues_spec.rb +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -99,7 +99,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end shared_examples 'project issues statistics' do - it 'returns project issues statistics' do + it 'returns project issues statistics', :aggregate_failures do get api("/projects/#{project.id}/issues_statistics", current_user), params: params expect(response).to have_gitlab_http_status(:ok) @@ -317,7 +317,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end it 'returns project confidential issues for admin' do - get api("#{base_url}/issues", admin) + get api("#{base_url}/issues", admin, admin_mode: true) expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end @@ -526,7 +526,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id]) end - it 'exposes known attributes' do + it 'exposes known attributes', :aggregate_failures do get api("#{base_url}/issues", user) expect(response).to have_gitlab_http_status(:ok) @@ -607,28 +607,28 @@ RSpec.describe API::Issues, feature_category: :team_planning do let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } - it 'returns issues by assignee_username' do + it 'returns issues by assignee_username', :aggregate_failures do get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' } expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect_paginated_array_response([confidential_issue.id, issue3.id]) end - it 'returns issues by assignee_username as string' do + it 'returns issues by assignee_username as string', :aggregate_failures do get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' } expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect_paginated_array_response([confidential_issue.id, issue3.id]) end - it 'returns error when multiple assignees are passed' do + it 'returns error when multiple assignees are passed', :aggregate_failures do get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response["error"]).to include("allows one value, but found 2") end - it 'returns error when assignee_username and assignee_id are passed together' do + it 'returns error when assignee_username and assignee_id are passed together', :aggregate_failures do get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' } expect(response).to have_gitlab_http_status(:bad_request) @@ -646,7 +646,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'exposes known attributes' do + it 'exposes known attributes', :aggregate_failures do get api("/projects/#{project.id}/issues/#{issue.iid}", user) expect(response).to have_gitlab_http_status(:ok) @@ -686,7 +686,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'exposes the closed_at attribute' do + it 'exposes the closed_at attribute', :aggregate_failures do get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user) expect(response).to have_gitlab_http_status(:ok) @@ -694,7 +694,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'links exposure' do - it 'exposes related resources full URIs' do + it 'exposes related resources full URIs', :aggregate_failures do get api("/projects/#{project.id}/issues/#{issue.iid}", user) links = json_response['_links'] @@ -706,7 +706,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'returns a project issue by internal id' do + it 'returns a project issue by internal id', :aggregate_failures do get api("/projects/#{project.id}/issues/#{issue.iid}", user) expect(response).to have_gitlab_http_status(:ok) @@ -738,7 +738,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(response).to have_gitlab_http_status(:not_found) end - it 'returns confidential issue for project members' do + it 'returns confidential issue for project members', :aggregate_failures do get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user) expect(response).to have_gitlab_http_status(:ok) @@ -746,7 +746,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['iid']).to eq(confidential_issue.iid) end - it 'returns confidential issue for author' do + it 'returns confidential issue for author', :aggregate_failures do get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author) expect(response).to have_gitlab_http_status(:ok) @@ -754,7 +754,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['iid']).to eq(confidential_issue.iid) end - it 'returns confidential issue for assignee' do + it 'returns confidential issue for assignee', :aggregate_failures do get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee) expect(response).to have_gitlab_http_status(:ok) @@ -762,8 +762,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['iid']).to eq(confidential_issue.iid) end - it 'returns confidential issue for admin' do - get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin) + it 'returns confidential issue for admin', :aggregate_failures do + get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response['title']).to eq(confidential_issue.title) @@ -829,7 +829,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do let!(:related_mr) { create_referencing_mr(user, project, issue) } context 'when unauthenticated' do - it 'return list of referenced merge requests from issue' do + it 'return list of referenced merge requests from issue', :aggregate_failures do get_related_merge_requests(project.id, issue.iid) expect_paginated_array_response(related_mr.id) @@ -898,8 +898,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'exposes known attributes' do - get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin) + it 'exposes known attributes', :aggregate_failures do + get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) @@ -936,7 +936,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do ) end - it 'returns a full list of participants' do + it 'returns a full list of participants', :aggregate_failures do get api("/projects/#{project.id}/issues/#{issue.iid}/participants", user) expect(response).to have_gitlab_http_status(:ok) @@ -945,7 +945,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when user cannot see a confidential note' do - it 'returns a limited list of participants' do + it 'returns a limited list of participants', :aggregate_failures do get api("/projects/#{project.id}/issues/#{issue.iid}/participants", create(:user)) expect(response).to have_gitlab_http_status(:ok) diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 4b60eaadcbc..33f49cefc69 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -78,7 +78,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end shared_examples 'issues statistics' do - it 'returns issues statistics' do + it 'returns issues statistics', :aggregate_failures do get api("/issues_statistics", user), params: params expect(response).to have_gitlab_http_status(:ok) @@ -109,8 +109,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do context 'as an admin' do context 'when issue exists' do - it 'returns the issue' do - get api("/issues/#{issue.id}", admin) + it 'returns the issue', :aggregate_failures do + get api("/issues/#{issue.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response.dig('author', 'id')).to eq(issue.author.id) @@ -121,7 +121,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do context 'when issue does not exist' do it 'returns 404' do - get api("/issues/0", admin) + get api("/issues/0", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -132,7 +132,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do describe 'GET /issues' do context 'when unauthenticated' do - it 'returns an array of all issues' do + it 'returns an array of all issues', :aggregate_failures do get api('/issues'), params: { scope: 'all' } expect(response).to have_gitlab_http_status(:ok) @@ -162,14 +162,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(response).to have_gitlab_http_status(:unauthorized) end - it 'returns an array of issues matching state in milestone' do + it 'returns an array of issues matching state in milestone', :aggregate_failures do get api('/issues'), params: { milestone: 'foo', scope: 'all' } expect(response).to have_gitlab_http_status(:ok) expect_paginated_array_response([]) end - it 'returns an array of issues matching state in milestone' do + it 'returns an array of issues matching state in milestone', :aggregate_failures do get api('/issues'), params: { milestone: milestone.title, scope: 'all' } expect(response).to have_gitlab_http_status(:ok) @@ -273,7 +273,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when authenticated' do - it 'returns an array of issues' do + it 'returns an array of issues', :aggregate_failures do get api('/issues', user) expect_paginated_array_response([issue.id, closed_issue.id]) @@ -532,7 +532,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do context 'with incident issues' do let_it_be(:incident) { create(:incident, project: project) } - it 'avoids N+1 queries' do + it 'avoids N+1 queries', :aggregate_failures do get api('/issues', user) # warm up control = ActiveRecord::QueryRecorder.new do @@ -553,7 +553,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do context 'with issues closed as duplicates' do let_it_be(:dup_issue_1) { create(:issue, :closed_as_duplicate, project: project) } - it 'avoids N+1 queries' do + it 'avoids N+1 queries', :aggregate_failures do get api('/issues', user) # warm up control = ActiveRecord::QueryRecorder.new do @@ -639,7 +639,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect_paginated_array_response([]) end - it 'returns an array of labeled issues matching given state' do + it 'returns an array of labeled issues matching given state', :aggregate_failures do get api('/issues', user), params: { labels: label.title, state: :opened } expect_paginated_array_response(issue.id) @@ -647,7 +647,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response.first['state']).to eq('opened') end - it 'returns an array of labeled issues matching given state with labels param as array' do + it 'returns an array of labeled issues matching given state with labels param as array', :aggregate_failures do get api('/issues', user), params: { labels: [label.title], state: :opened } expect_paginated_array_response(issue.id) @@ -917,14 +917,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'matches V4 response schema' do + it 'matches V4 response schema', :aggregate_failures do get api('/issues', user) expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('public_api/v4/issues') end - it 'returns a related merge request count of 0 if there are no related merge requests' do + it 'returns a related merge request count of 0 if there are no related merge requests', :aggregate_failures do get api('/issues', user) expect(response).to have_gitlab_http_status(:ok) @@ -932,7 +932,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response.first).to include('merge_requests_count' => 0) end - it 'returns a related merge request count > 0 if there are related merge requests' do + it 'returns a related merge request count > 0 if there are related merge requests', :aggregate_failures do create(:merge_requests_closing_issues, issue: issue) get api('/issues', user) @@ -1013,28 +1013,28 @@ RSpec.describe API::Issues, feature_category: :team_planning do let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } - it 'returns issues with by assignee_username' do + it 'returns issues with by assignee_username', :aggregate_failures do get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' } expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect_paginated_array_response([confidential_issue.id, issue3.id]) end - it 'returns issues by assignee_username as string' do + it 'returns issues by assignee_username as string', :aggregate_failures do get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' } expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect_paginated_array_response([confidential_issue.id, issue3.id]) end - it 'returns error when multiple assignees are passed' do + it 'returns error when multiple assignees are passed', :aggregate_failures do get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response["error"]).to include("allows one value, but found 2") end - it 'returns error when assignee_username and assignee_id are passed together' do + it 'returns error when assignee_username and assignee_id are passed together', :aggregate_failures do get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' } expect(response).to have_gitlab_http_status(:bad_request) @@ -1088,7 +1088,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end describe 'GET /projects/:id/issues/:issue_iid' do - it 'exposes full reference path' do + it 'exposes full reference path', :aggregate_failures do get api("/projects/#{project.id}/issues/#{issue.iid}", user) expect(response).to have_gitlab_http_status(:ok) @@ -1106,7 +1106,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'user does not have permission to view new issue' do - it 'does not return the issue as closed_as_duplicate_of' do + it 'does not return the issue as closed_as_duplicate_of', :aggregate_failures do get api("/projects/#{project.id}/issues/#{issue_closed_as_dup.iid}", user) expect(response).to have_gitlab_http_status(:ok) @@ -1119,7 +1119,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do new_issue.project.add_guest(user) end - it 'returns the issue as closed_as_duplicate_of' do + it 'returns the issue as closed_as_duplicate_of', :aggregate_failures do get api("/projects/#{project.id}/issues/#{issue_closed_as_dup.iid}", user) expect(response).to have_gitlab_http_status(:ok) @@ -1131,7 +1131,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end describe "POST /projects/:id/issues" do - it 'creates a new project issue' do + it 'creates a new project issue', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue' } expect(response).to have_gitlab_http_status(:created) @@ -1139,6 +1139,15 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['issue_type']).to eq('issue') end + context 'when confidential is null' do + it 'responds with 400 error', :aggregate_failures do + post api("/projects/#{project.id}/issues", user), params: { title: 'issue', confidential: nil } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq('confidential is empty') + end + end + context 'when issue create service returns an unrecoverable error' do before do allow_next_instance_of(Issues::CreateService) do |create_service| @@ -1146,7 +1155,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'returns and error message and status code from the service' do + it 'returns and error message and status code from the service', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue' } expect(response).to have_gitlab_http_status(:forbidden) @@ -1168,15 +1177,15 @@ RSpec.describe API::Issues, feature_category: :team_planning do travel_to fixed_time end - it 'allows admins to set the timestamp' do - put api("/projects/#{project.id}/issues/#{issue.iid}", admin), params: { labels: 'label1', updated_at: updated_at } + it 'allows admins to set the timestamp', :aggregate_failures do + put api("/projects/#{project.id}/issues/#{issue.iid}", admin, admin_mode: true), params: { labels: 'label1', updated_at: updated_at } expect(response).to have_gitlab_http_status(:ok) expect(Time.parse(json_response['updated_at'])).to be_like_time(updated_at) expect(ResourceLabelEvent.last.created_at).to be_like_time(updated_at) end - it 'does not allow other users to set the timestamp' do + it 'does not allow other users to set the timestamp', :aggregate_failures do reporter = create(:user) project.add_developer(reporter) @@ -1259,7 +1268,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'with valid params' do - it 'reorders issues and returns a successful 200 response' do + it 'reorders issues and returns a successful 200 response', :aggregate_failures do put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id } expect(response).to have_gitlab_http_status(:ok) @@ -1286,7 +1295,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do let(:other_project) { create(:project, group: group) } let(:other_issue) { create(:issue, project: other_project, relative_position: 80) } - it 'reorders issues and returns a successful 200 response' do + it 'reorders issues and returns a successful 200 response', :aggregate_failures do put api("/projects/#{other_project.id}/issues/#{other_issue.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id } expect(response).to have_gitlab_http_status(:ok) diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb index 265091fa698..a17c1389e83 100644 --- a/spec/requests/api/issues/post_projects_issues_spec.rb +++ b/spec/requests/api/issues/post_projects_issues_spec.rb @@ -75,7 +75,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do describe 'POST /projects/:id/issues' do context 'support for deprecated assignee_id' do - it 'creates a new project issue' do + it 'creates a new project issue', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', assignee_id: user2.id } @@ -85,7 +85,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['assignees'].first['name']).to eq(user2.name) end - it 'creates a new project issue when assignee_id is empty' do + it 'creates a new project issue when assignee_id is empty', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', assignee_id: '' } @@ -96,7 +96,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'single assignee restrictions' do - it 'creates a new project issue with no more than one assignee' do + it 'creates a new project issue with no more than one assignee', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', assignee_ids: [user2.id, guest.id] } @@ -122,8 +122,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do context 'an internal ID is provided' do context 'by an admin' do - it 'sets the internal ID on the new issue' do - post api("/projects/#{project.id}/issues", admin), + it 'sets the internal ID on the new issue', :aggregate_failures do + post api("/projects/#{project.id}/issues", admin, admin_mode: true), params: { title: 'new issue', iid: 9001 } expect(response).to have_gitlab_http_status(:created) @@ -132,7 +132,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'by an owner' do - it 'sets the internal ID on the new issue' do + it 'sets the internal ID on the new issue', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', iid: 9001 } @@ -145,7 +145,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do let(:group) { create(:group) } let(:group_project) { create(:project, :public, namespace: group) } - it 'sets the internal ID on the new issue' do + it 'sets the internal ID on the new issue', :aggregate_failures do group.add_owner(user2) post api("/projects/#{group_project.id}/issues", user2), params: { title: 'new issue', iid: 9001 } @@ -156,7 +156,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'by another user' do - it 'ignores the given internal ID' do + it 'ignores the given internal ID', :aggregate_failures do post api("/projects/#{project.id}/issues", user2), params: { title: 'new issue', iid: 9001 } @@ -166,8 +166,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when an issue with the same IID exists on database' do - it 'returns 409' do - post api("/projects/#{project.id}/issues", admin), + it 'returns 409', :aggregate_failures do + post api("/projects/#{project.id}/issues", admin, admin_mode: true), params: { title: 'new issue', iid: issue.iid } expect(response).to have_gitlab_http_status(:conflict) @@ -176,7 +176,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'creates a new project issue' do + it 'creates a new project issue', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] } @@ -189,7 +189,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['assignees'].first['name']).to eq(user2.name) end - it 'creates a new project issue with labels param as array' do + it 'creates a new project issue with labels param as array', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', labels: %w(label label2), weight: 3, assignee_ids: [user2.id] } @@ -202,7 +202,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['assignees'].first['name']).to eq(user2.name) end - it 'creates a new confidential project issue' do + it 'creates a new confidential project issue', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', confidential: true } @@ -211,7 +211,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['confidential']).to be_truthy end - it 'creates a new confidential project issue with a different param' do + it 'creates a new confidential project issue with a different param', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', confidential: 'y' } @@ -220,7 +220,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['confidential']).to be_truthy end - it 'creates a public issue when confidential param is false' do + it 'creates a public issue when confidential param is false', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', confidential: false } @@ -229,7 +229,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['confidential']).to be_falsy end - it 'creates a public issue when confidential param is invalid' do + it 'creates a public issue when confidential param is invalid', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', confidential: 'foo' } @@ -242,7 +242,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(response).to have_gitlab_http_status(:bad_request) end - it 'allows special label names' do + it 'allows special label names', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', @@ -256,7 +256,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['labels']).to include '&' end - it 'allows special label names with labels param as array' do + it 'allows special label names with labels param as array', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'new issue', @@ -270,7 +270,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['labels']).to include '&' end - it 'returns 400 if title is too long' do + it 'returns 400 if title is too long', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: { title: 'g' * 256 } expect(response).to have_gitlab_http_status(:bad_request) @@ -313,7 +313,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'with due date' do - it 'creates a new project issue' do + it 'creates a new project issue', :aggregate_failures do due_date = 2.weeks.from_now.strftime('%Y-%m-%d') post api("/projects/#{project.id}/issues", user), @@ -336,8 +336,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'by an admin' do - it 'sets the creation time on the new issue' do - post api("/projects/#{project.id}/issues", admin), params: params + it 'sets the creation time on the new issue', :aggregate_failures do + post api("/projects/#{project.id}/issues", admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:created) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) @@ -346,7 +346,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'by a project owner' do - it 'sets the creation time on the new issue' do + it 'sets the creation time on the new issue', :aggregate_failures do post api("/projects/#{project.id}/issues", user), params: params expect(response).to have_gitlab_http_status(:created) @@ -356,7 +356,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'by a group owner' do - it 'sets the creation time on the new issue' do + it 'sets the creation time on the new issue', :aggregate_failures do group = create(:group) group_project = create(:project, :public, namespace: group) group.add_owner(user2) @@ -370,7 +370,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'by another user' do - it 'ignores the given creation time' do + it 'ignores the given creation time', :aggregate_failures do project.add_developer(user2) post api("/projects/#{project.id}/issues", user2), params: params @@ -397,7 +397,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when request exceeds the rate limit' do - it 'prevents users from creating more issues' do + it 'prevents users from creating more issues', :aggregate_failures do allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true) post api("/projects/#{project.id}/issues", user), @@ -437,7 +437,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect { post_issue }.not_to change(Issue, :count) end - it 'returns correct status and message' do + it 'returns correct status and message', :aggregate_failures do post_issue expect(response).to have_gitlab_http_status(:bad_request) @@ -476,7 +476,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace) } - it 'moves an issue' do + it 'moves an issue', :aggregate_failures do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), params: { to_project_id: target_project.id } @@ -485,7 +485,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when source and target projects are the same' do - it 'returns 400 when trying to move an issue' do + it 'returns 400 when trying to move an issue', :aggregate_failures do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), params: { to_project_id: project.id } @@ -495,7 +495,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when the user does not have the permission to move issues' do - it 'returns 400 when trying to move an issue' do + it 'returns 400 when trying to move an issue', :aggregate_failures do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), params: { to_project_id: target_project2.id } @@ -504,8 +504,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'moves the issue to another namespace if I am admin' do - post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin), + it 'moves the issue to another namespace if I am admin', :aggregate_failures do + post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin, admin_mode: true), params: { to_project_id: target_project2.id } expect(response).to have_gitlab_http_status(:created) @@ -513,7 +513,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when using the issue ID instead of iid' do - it 'returns 404 when trying to move an issue', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do + it 'returns 404 when trying to move an issue', :aggregate_failures, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), params: { to_project_id: target_project.id } @@ -523,7 +523,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when issue does not exist' do - it 'returns 404 when trying to move an issue' do + it 'returns 404 when trying to move an issue', :aggregate_failures do post api("/projects/#{project.id}/issues/123/move", user), params: { to_project_id: target_project.id } @@ -533,7 +533,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when source project does not exist' do - it 'returns 404 when trying to move an issue' do + it 'returns 404 when trying to move an issue', :aggregate_failures do post api("/projects/0/issues/#{issue.iid}/move", user), params: { to_project_id: target_project.id } @@ -562,7 +562,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do context 'when user can admin the issue' do context 'when the user can admin the target project' do - it 'clones the issue' do + it 'clones the issue', :aggregate_failures do expect do post_clone_issue(user, issue, valid_target_project) end.to change { valid_target_project.issues.count }.by(1) @@ -577,7 +577,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when target project is the same source project' do - it 'clones the issue' do + it 'clones the issue', :aggregate_failures do expect do post_clone_issue(user, issue, issue.project) end.to change { issue.reset.project.issues.count }.by(1) @@ -595,7 +595,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when the user does not have the permission to clone issues' do - it 'returns 400' do + it 'returns 400', :aggregate_failures do post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user), params: { to_project_id: invalid_target_project.id } @@ -605,7 +605,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when using the issue ID instead of iid' do - it 'returns 404', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do + it 'returns 404', :aggregate_failures, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do post api("/projects/#{project.id}/issues/#{issue.id}/clone", user), params: { to_project_id: valid_target_project.id } @@ -615,7 +615,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when issue does not exist' do - it 'returns 404' do + it 'returns 404', :aggregate_failures do post api("/projects/#{project.id}/issues/12300/clone", user), params: { to_project_id: valid_target_project.id } @@ -625,7 +625,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when source project does not exist' do - it 'returns 404' do + it 'returns 404', :aggregate_failures do post api("/projects/0/issues/#{issue.iid}/clone", user), params: { to_project_id: valid_target_project.id } @@ -635,7 +635,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end context 'when target project does not exist' do - it 'returns 404' do + it 'returns 404', :aggregate_failures do post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user), params: { to_project_id: 0 } @@ -644,7 +644,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'clones the issue with notes when with_notes is true' do + it 'clones the issue with notes when with_notes is true', :aggregate_failures do expect do post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user), params: { to_project_id: valid_target_project.id, with_notes: true } @@ -661,7 +661,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end describe 'POST :id/issues/:issue_iid/subscribe' do - it 'subscribes to an issue' do + it 'subscribes to an issue', :aggregate_failures do post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2) expect(response).to have_gitlab_http_status(:created) @@ -694,7 +694,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end describe 'POST :id/issues/:issue_id/unsubscribe' do - it 'unsubscribes from an issue' do + it 'unsubscribes from an issue', :aggregate_failures do post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user) expect(response).to have_gitlab_http_status(:created) diff --git a/spec/requests/api/issues/put_projects_issues_spec.rb b/spec/requests/api/issues/put_projects_issues_spec.rb index f0d174c9e78..6cc639c0bcc 100644 --- a/spec/requests/api/issues/put_projects_issues_spec.rb +++ b/spec/requests/api/issues/put_projects_issues_spec.rb @@ -80,7 +80,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end describe 'PUT /projects/:id/issues/:issue_iid to update only title' do - it 'updates a project issue' do + it 'updates a project issue', :aggregate_failures do put api_for_user, params: { title: updated_title } expect(response).to have_gitlab_http_status(:ok) @@ -109,7 +109,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(response).to have_gitlab_http_status(:ok) end - it 'allows special label names with labels param as array' do + it 'allows special label names with labels param as array', :aggregate_failures do put api_for_user, params: { title: updated_title, @@ -135,42 +135,42 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(response).to have_gitlab_http_status(:forbidden) end - it 'updates a confidential issue for project members' do + it 'updates a confidential issue for project members', :aggregate_failures do put api(confidential_issue_path, user), params: { title: updated_title } expect(response).to have_gitlab_http_status(:ok) expect(json_response['title']).to eq(updated_title) end - it 'updates a confidential issue for author' do + it 'updates a confidential issue for author', :aggregate_failures do put api(confidential_issue_path, author), params: { title: updated_title } expect(response).to have_gitlab_http_status(:ok) expect(json_response['title']).to eq(updated_title) end - it 'updates a confidential issue for admin' do - put api(confidential_issue_path, admin), params: { title: updated_title } + it 'updates a confidential issue for admin', :aggregate_failures do + put api(confidential_issue_path, admin, admin_mode: true), params: { title: updated_title } expect(response).to have_gitlab_http_status(:ok) expect(json_response['title']).to eq(updated_title) end - it 'sets an issue to confidential' do + it 'sets an issue to confidential', :aggregate_failures do put api_for_user, params: { confidential: true } expect(response).to have_gitlab_http_status(:ok) expect(json_response['confidential']).to be_truthy end - it 'makes a confidential issue public' do + it 'makes a confidential issue public', :aggregate_failures do put api(confidential_issue_path, user), params: { confidential: false } expect(response).to have_gitlab_http_status(:ok) expect(json_response['confidential']).to be_falsy end - it 'does not update a confidential issue with wrong confidential flag' do + it 'does not update a confidential issue with wrong confidential flag', :aggregate_failures do put api(confidential_issue_path, user), params: { confidential: 'foo' } expect(response).to have_gitlab_http_status(:bad_request) @@ -209,7 +209,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect { update_issue }.not_to change { issue.reload.title } end - it 'returns correct status and message' do + it 'returns correct status and message', :aggregate_failures do update_issue expect(response).to have_gitlab_http_status(:bad_request) @@ -246,14 +246,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do describe 'PUT /projects/:id/issues/:issue_iid to update assignee' do context 'support for deprecated assignee_id' do - it 'removes assignee' do + it 'removes assignee', :aggregate_failures do put api_for_user, params: { assignee_id: 0 } expect(response).to have_gitlab_http_status(:ok) expect(json_response['assignee']).to be_nil end - it 'updates an issue with new assignee' do + it 'updates an issue with new assignee', :aggregate_failures do put api_for_user, params: { assignee_id: user2.id } expect(response).to have_gitlab_http_status(:ok) @@ -261,21 +261,21 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'removes assignee' do + it 'removes assignee', :aggregate_failures do put api_for_user, params: { assignee_ids: [0] } expect(response).to have_gitlab_http_status(:ok) expect(json_response['assignees']).to be_empty end - it 'updates an issue with new assignee' do + it 'updates an issue with new assignee', :aggregate_failures do put api_for_user, params: { assignee_ids: [user2.id] } expect(response).to have_gitlab_http_status(:ok) expect(json_response['assignees'].first['name']).to eq(user2.name) end - context 'single assignee restrictions' do + context 'single assignee restrictions', :aggregate_failures do it 'updates an issue with several assignees but only one has been applied' do put api_for_user, params: { assignee_ids: [user2.id, guest.id] } @@ -289,7 +289,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do let!(:label) { create(:label, title: 'dummy', project: project) } let!(:label_link) { create(:label_link, label: label, target: issue) } - it 'adds relevant labels' do + it 'adds relevant labels', :aggregate_failures do put api_for_user, params: { add_labels: '1, 2' } expect(response).to have_gitlab_http_status(:ok) @@ -300,14 +300,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do let!(:label2) { create(:label, title: 'a-label', project: project) } let!(:label_link2) { create(:label_link, label: label2, target: issue) } - it 'removes relevant labels' do + it 'removes relevant labels', :aggregate_failures do put api_for_user, params: { remove_labels: label2.title } expect(response).to have_gitlab_http_status(:ok) expect(json_response['labels']).to eq([label.title]) end - it 'removes all labels' do + it 'removes all labels', :aggregate_failures do put api_for_user, params: { remove_labels: "#{label.title}, #{label2.title}" } expect(response).to have_gitlab_http_status(:ok) @@ -315,14 +315,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do end end - it 'does not update labels if not present' do + it 'does not update labels if not present', :aggregate_failures do put api_for_user, params: { title: updated_title } expect(response).to have_gitlab_http_status(:ok) expect(json_response['labels']).to eq([label.title]) end - it 'removes all labels and touches the record' do + it 'removes all labels and touches the record', :aggregate_failures do travel_to(2.minutes.from_now) do put api_for_user, params: { labels: '' } end @@ -332,7 +332,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['updated_at']).to be > Time.current end - it 'removes all labels and touches the record with labels param as array' do + it 'removes all labels and touches the record with labels param as array', :aggregate_failures do travel_to(2.minutes.from_now) do put api_for_user, params: { labels: [''] } end @@ -342,7 +342,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['updated_at']).to be > Time.current end - it 'updates labels and touches the record' do + it 'updates labels and touches the record', :aggregate_failures do travel_to(2.minutes.from_now) do put api_for_user, params: { labels: 'foo,bar' } end @@ -352,7 +352,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['updated_at']).to be > Time.current end - it 'updates labels and touches the record with labels param as array' do + it 'updates labels and touches the record with labels param as array', :aggregate_failures do travel_to(2.minutes.from_now) do put api_for_user, params: { labels: %w(foo bar) } end @@ -363,21 +363,21 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['updated_at']).to be > Time.current end - it 'allows special label names' do + it 'allows special label names', :aggregate_failures do put api_for_user, params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' } expect(response).to have_gitlab_http_status(:ok) expect(json_response['labels']).to contain_exactly('label:foo', 'label-bar', 'label_bar', 'label/bar', 'label?bar', 'label&bar', '?', '&') end - it 'allows special label names with labels param as array' do + it 'allows special label names with labels param as array', :aggregate_failures do put api_for_user, params: { labels: ['label:foo', 'label-bar', 'label_bar', 'label/bar,label?bar,label&bar,?,&'] } expect(response).to have_gitlab_http_status(:ok) expect(json_response['labels']).to contain_exactly('label:foo', 'label-bar', 'label_bar', 'label/bar', 'label?bar', 'label&bar', '?', '&') end - it 'returns 400 if title is too long' do + it 'returns 400 if title is too long', :aggregate_failures do put api_for_user, params: { title: 'g' * 256 } expect(response).to have_gitlab_http_status(:bad_request) @@ -386,7 +386,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end describe 'PUT /projects/:id/issues/:issue_iid to update state and label' do - it 'updates a project issue' do + it 'updates a project issue', :aggregate_failures do put api_for_user, params: { labels: 'label2', state_event: 'close' } expect(response).to have_gitlab_http_status(:ok) @@ -394,7 +394,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(json_response['state']).to eq 'closed' end - it 'reopens a project isssue' do + it 'reopens a project isssue', :aggregate_failures do put api(issue_path, user), params: { state_event: 'reopen' } expect(response).to have_gitlab_http_status(:ok) @@ -404,7 +404,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do describe 'PUT /projects/:id/issues/:issue_iid to update updated_at param' do context 'when reporter makes request' do - it 'accepts the update date to be set' do + it 'accepts the update date to be set', :aggregate_failures do update_time = 2.weeks.ago put api_for_user, params: { title: 'some new title', updated_at: update_time } @@ -436,7 +436,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do expect(response).to have_gitlab_http_status(:bad_request) end - it 'accepts the update date to be set' do + it 'accepts the update date to be set', :aggregate_failures do update_time = 2.weeks.ago put api_for_owner, params: { title: 'some new title', updated_at: update_time } @@ -448,7 +448,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do end describe 'PUT /projects/:id/issues/:issue_iid to update due date' do - it 'creates a new project issue' do + it 'creates a new project issue', :aggregate_failures do due_date = 2.weeks.from_now.strftime('%Y-%m-%d') put api_for_user, params: { due_date: due_date } diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index d9a0f061156..0ca1a7d030f 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Keys, feature_category: :authentication_and_authorization do +RSpec.describe API::Keys, feature_category: :system_access do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:admin) } let_it_be(:email) { create(:email, user: user) } diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index 82b87007a9b..3f131862a41 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Lint, feature_category: :pipeline_authoring do +RSpec.describe API::Lint, feature_category: :pipeline_composition do describe 'POST /ci/lint' do context 'when signup settings are disabled' do before do @@ -245,8 +245,8 @@ RSpec.describe API::Lint, feature_category: :pipeline_authoring do it 'passes validation' do ci_lint - included_config = YAML.safe_load(included_content, [Symbol]) - root_config = YAML.safe_load(yaml_content, [Symbol]) + included_config = YAML.safe_load(included_content, permitted_classes: [Symbol]) + root_config = YAML.safe_load(yaml_content, permitted_classes: [Symbol]) expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml expect(response).to have_gitlab_http_status(:ok) @@ -535,8 +535,8 @@ RSpec.describe API::Lint, feature_category: :pipeline_authoring do it 'passes validation' do ci_lint - included_config = YAML.safe_load(included_content, [Symbol]) - root_config = YAML.safe_load(yaml_content, [Symbol]) + included_config = YAML.safe_load(included_content, permitted_classes: [Symbol]) + root_config = YAML.safe_load(yaml_content, permitted_classes: [Symbol]) expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml expect(response).to have_gitlab_http_status(:ok) diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb index 20aa660d95b..7b850fed79c 100644 --- a/spec/requests/api/maven_packages_spec.rb +++ b/spec/requests/api/maven_packages_spec.rb @@ -22,7 +22,7 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) } let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_maven_user' } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_maven_user' } } let(:package_name) { 'com/example/my-app' } let(:headers) { workhorse_headers } @@ -285,6 +285,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do describe 'GET /api/v4/packages/maven/*path/:file_name' do context 'a public project' do + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_maven_user' } } + subject { download_file(file_name: package_file.file_name) } shared_examples 'getting a file' do @@ -451,6 +453,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do it_behaves_like 'forwarding package requests' context 'a public project' do + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_maven_user' } } + subject { download_file(file_name: package_file.file_name) } shared_examples 'getting a file for a group' do @@ -660,6 +664,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do context 'a public project' do + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_maven_user' } } + subject { download_file(file_name: package_file.file_name) } it_behaves_like 'tracking the file download event' @@ -901,8 +907,6 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do it_behaves_like 'package workhorse uploads' context 'event tracking' do - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_maven_user' } } - it_behaves_like 'a package tracking event', described_class.name, 'push_package' context 'when the package file fails to be created' do @@ -962,6 +966,17 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do expect(response).to have_gitlab_http_status(:forbidden) end + context 'file name is too long' do + let(:file_name) { 'a' * (Packages::Maven::FindOrCreatePackageService::MAX_FILE_NAME_LENGTH + 1) } + + it 'rejects request' do + expect { upload_file_with_token(params: params, file_name: file_name) }.not_to change { project.packages.count } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to include('File name is too long') + end + end + context 'version is not correct' do let(:version) { '$%123' } @@ -981,9 +996,9 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do package_settings.update!(maven_duplicates_allowed: false) end - shared_examples 'storing the package file' do + shared_examples 'storing the package file' do |file_name: 'my-app-1.0-20180724.124855-1'| it 'stores the file', :aggregate_failures do - expect { upload_file_with_token(params: params) }.to change { package.package_files.count }.by(1) + expect { upload_file_with_token(params: params, file_name: file_name) }.to change { package.package_files.count }.by(1) expect(response).to have_gitlab_http_status(:ok) expect(jar_file.file_name).to eq(file_upload.original_filename) @@ -1023,6 +1038,10 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do it_behaves_like 'storing the package file' end + + context 'when uploading a similar package file name with a classifier' do + it_behaves_like 'storing the package file', file_name: 'my-app-1.0-20180724.124855-1-javadoc' + end end context 'for sha1 file' do @@ -1088,8 +1107,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do end end - def upload_file(params: {}, request_headers: headers, file_extension: 'jar') - url = "/projects/#{project.id}/packages/maven/#{param_path}/my-app-1.0-20180724.124855-1.#{file_extension}" + def upload_file(params: {}, request_headers: headers, file_extension: 'jar', file_name: 'my-app-1.0-20180724.124855-1') + url = "/projects/#{project.id}/packages/maven/#{param_path}/#{file_name}.#{file_extension}" workhorse_finalize( api(url), method: :put, @@ -1100,8 +1119,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do ) end - def upload_file_with_token(params: {}, request_headers: headers_with_token, file_extension: 'jar') - upload_file(params: params, request_headers: request_headers, file_extension: file_extension) + def upload_file_with_token(params: {}, request_headers: headers_with_token, file_extension: 'jar', file_name: 'my-app-1.0-20180724.124855-1') + upload_file(params: params, request_headers: request_headers, file_name: file_name, file_extension: file_extension) end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 19a630e5218..81815fdab62 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -168,6 +168,17 @@ RSpec.describe API::MergeRequests, feature_category: :source_code_management do end end + context 'when DB timeouts occur' do + it 'returns a :request_timeout status' do + allow(MergeRequestsFinder).to receive(:new).and_raise(ActiveRecord::QueryCanceled) + + path = endpoint_path + '?view=simple' + get api(path, user) + + expect(response).to have_gitlab_http_status(:request_timeout) + end + end + it 'returns an array of all merge_requests using simple mode' do path = endpoint_path + '?view=simple' diff --git a/spec/requests/api/metadata_spec.rb b/spec/requests/api/metadata_spec.rb index b9bdadb01cc..e15186c48a5 100644 --- a/spec/requests/api/metadata_spec.rb +++ b/spec/requests/api/metadata_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Metadata, feature_category: :not_owned do +RSpec.describe API::Metadata, feature_category: :shared do shared_examples_for 'GET /metadata' do context 'when unauthenticated' do it 'returns authentication error' do diff --git a/spec/requests/api/npm_instance_packages_spec.rb b/spec/requests/api/npm_instance_packages_spec.rb index dcd2e4ae677..591a8ee68dc 100644 --- a/spec/requests/api/npm_instance_packages_spec.rb +++ b/spec/requests/api/npm_instance_packages_spec.rb @@ -11,8 +11,38 @@ RSpec.describe API::NpmInstancePackages, feature_category: :package_registry do include_context 'npm api setup' describe 'GET /api/v4/packages/npm/*package_name' do - it_behaves_like 'handling get metadata requests', scope: :instance do - let(:url) { api("/packages/npm/#{package_name}") } + let(:url) { api("/packages/npm/#{package_name}") } + + it_behaves_like 'handling get metadata requests', scope: :instance + + context 'with a duplicate package name in another project' do + subject { get(url) } + + let_it_be(:project2) { create(:project, :public, namespace: namespace) } + let_it_be(:package2) do + create(:npm_package, + project: project2, + name: "@#{group.path}/scoped_package", + version: '1.2.0') + end + + it 'includes all matching package versions in the response' do + subject + + expect(json_response['versions'].keys).to match_array([package.version, package2.version]) + end + + context 'with the feature flag disabled' do + before do + stub_feature_flags(npm_allow_packages_in_multiple_projects: false) + end + + it 'returns matching package versions from only one project' do + subject + + expect(json_response['versions'].keys).to match_array([package2.version]) + end + end end end diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb index c62c0849776..2f67e1e8eea 100644 --- a/spec/requests/api/npm_project_packages_spec.rb +++ b/spec/requests/api/npm_project_packages_spec.rb @@ -115,6 +115,8 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do end context 'private project' do + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_npm_user' } } + before do project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) end @@ -143,6 +145,8 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do end context 'internal project' do + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_npm_user' } } + before do project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) end diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb index 4335ad75ab6..facbc01220d 100644 --- a/spec/requests/api/nuget_group_packages_spec.rb +++ b/spec/requests/api/nuget_group_packages_spec.rb @@ -12,8 +12,17 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do let_it_be(:deploy_token) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) } let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: group) } - let(:snowplow_gitlab_standard_context) { { namespace: project.group, property: 'i_package_nuget_user' } } let(:target_type) { 'groups' } + let(:snowplow_gitlab_standard_context) { snowplow_context } + let(:target) { subgroup } + + def snowplow_context(user_role: :developer) + if user_role == :anonymous + { namespace: target, property: 'i_package_nuget_user' } + else + { namespace: target, property: 'i_package_nuget_user', user: user } + end + end shared_examples 'handling all endpoints' do describe 'GET /api/v4/groups/:id/-/packages/nuget' do @@ -84,7 +93,6 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do context 'a group' do let(:target) { group } - let(:snowplow_gitlab_standard_context) { { namespace: target, property: 'i_package_nuget_user' } } it_behaves_like 'handling all endpoints' diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb index 1e0d35ad451..887dfd4beeb 100644 --- a/spec/requests/api/nuget_project_packages_spec.rb +++ b/spec/requests/api/nuget_project_packages_spec.rb @@ -13,7 +13,15 @@ RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do let(:target) { project } let(:target_type) { 'projects' } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_nuget_user' } } + let(:snowplow_gitlab_standard_context) { snowplow_context } + + def snowplow_context(user_role: :developer) + if user_role == :anonymous + { project: target, namespace: target.namespace, property: 'i_package_nuget_user' } + else + { project: target, namespace: target.namespace, property: 'i_package_nuget_user', user: user } + end + end shared_examples 'accept get request on private project with access to package registry for everyone' do subject { get api(url) } @@ -149,6 +157,7 @@ RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } subject { get api(url), headers: headers } diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb index b29f1e9e661..19a943477d2 100644 --- a/spec/requests/api/oauth_tokens_spec.rb +++ b/spec/requests/api/oauth_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'OAuth tokens', feature_category: :authentication_and_authorization do +RSpec.describe 'OAuth tokens', feature_category: :system_access do include HttpBasicAuthHelpers context 'Resource Owner Password Credentials' do @@ -124,6 +124,8 @@ RSpec.describe 'OAuth tokens', feature_category: :authentication_and_authorizati context 'when user account is not confirmed' do before do + stub_application_setting_enum('email_confirmation_setting', 'soft') + user.update!(confirmed_at: nil) request_oauth_token(user, client_basic_auth_header(client)) diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb index fdc25ecdcd3..3e7837866ae 100644 --- a/spec/requests/api/pages/internal_access_spec.rb +++ b/spec/requests/api/pages/internal_access_spec.rb @@ -35,39 +35,39 @@ RSpec.describe "Internal Project Pages Access", feature_category: :pages do describe "GET /projects/:id/pages_access" do context 'access depends on the level' do - where(:pages_access_level, :with_user, :expected_result) do - ProjectFeature::DISABLED | "admin" | 403 - ProjectFeature::DISABLED | "owner" | 403 - ProjectFeature::DISABLED | "master" | 403 - ProjectFeature::DISABLED | "developer" | 403 - ProjectFeature::DISABLED | "reporter" | 403 - ProjectFeature::DISABLED | "guest" | 403 - ProjectFeature::DISABLED | "user" | 403 - ProjectFeature::DISABLED | nil | 404 - ProjectFeature::PUBLIC | "admin" | 200 - ProjectFeature::PUBLIC | "owner" | 200 - ProjectFeature::PUBLIC | "master" | 200 - ProjectFeature::PUBLIC | "developer" | 200 - ProjectFeature::PUBLIC | "reporter" | 200 - ProjectFeature::PUBLIC | "guest" | 200 - ProjectFeature::PUBLIC | "user" | 200 - ProjectFeature::PUBLIC | nil | 404 - ProjectFeature::ENABLED | "admin" | 200 - ProjectFeature::ENABLED | "owner" | 200 - ProjectFeature::ENABLED | "master" | 200 - ProjectFeature::ENABLED | "developer" | 200 - ProjectFeature::ENABLED | "reporter" | 200 - ProjectFeature::ENABLED | "guest" | 200 - ProjectFeature::ENABLED | "user" | 200 - ProjectFeature::ENABLED | nil | 404 - ProjectFeature::PRIVATE | "admin" | 200 - ProjectFeature::PRIVATE | "owner" | 200 - ProjectFeature::PRIVATE | "master" | 200 - ProjectFeature::PRIVATE | "developer" | 200 - ProjectFeature::PRIVATE | "reporter" | 200 - ProjectFeature::PRIVATE | "guest" | 200 - ProjectFeature::PRIVATE | "user" | 403 - ProjectFeature::PRIVATE | nil | 404 + where(:pages_access_level, :with_user, :admin_mode, :expected_result) do + ProjectFeature::DISABLED | "admin" | true | 403 + ProjectFeature::DISABLED | "owner" | false | 403 + ProjectFeature::DISABLED | "master" | false | 403 + ProjectFeature::DISABLED | "developer" | false | 403 + ProjectFeature::DISABLED | "reporter" | false | 403 + ProjectFeature::DISABLED | "guest" | false | 403 + ProjectFeature::DISABLED | "user" | false | 403 + ProjectFeature::DISABLED | nil | false | 404 + ProjectFeature::PUBLIC | "admin" | false | 200 + ProjectFeature::PUBLIC | "owner" | false | 200 + ProjectFeature::PUBLIC | "master" | false | 200 + ProjectFeature::PUBLIC | "developer" | false | 200 + ProjectFeature::PUBLIC | "reporter" | false | 200 + ProjectFeature::PUBLIC | "guest" | false | 200 + ProjectFeature::PUBLIC | "user" | false | 200 + ProjectFeature::PUBLIC | nil | false | 404 + ProjectFeature::ENABLED | "admin" | false | 200 + ProjectFeature::ENABLED | "owner" | false | 200 + ProjectFeature::ENABLED | "master" | false | 200 + ProjectFeature::ENABLED | "developer" | false | 200 + ProjectFeature::ENABLED | "reporter" | false | 200 + ProjectFeature::ENABLED | "guest" | false | 200 + ProjectFeature::ENABLED | "user" | false | 200 + ProjectFeature::ENABLED | nil | false | 404 + ProjectFeature::PRIVATE | "admin" | true | 200 + ProjectFeature::PRIVATE | "owner" | false | 200 + ProjectFeature::PRIVATE | "master" | false | 200 + ProjectFeature::PRIVATE | "developer" | false | 200 + ProjectFeature::PRIVATE | "reporter" | false | 200 + ProjectFeature::PRIVATE | "guest" | false | 200 + ProjectFeature::PRIVATE | "user" | false | 403 + ProjectFeature::PRIVATE | nil | false | 404 end with_them do @@ -77,7 +77,7 @@ RSpec.describe "Internal Project Pages Access", feature_category: :pages do it "correct return value" do if !with_user.nil? user = public_send(with_user) - get api("/projects/#{project.id}/pages_access", user) + get api("/projects/#{project.id}/pages_access", user, admin_mode: admin_mode) else get api("/projects/#{project.id}/pages_access") end diff --git a/spec/requests/api/pages/pages_spec.rb b/spec/requests/api/pages/pages_spec.rb index c426f2a433c..0f6675799ad 100644 --- a/spec/requests/api/pages/pages_spec.rb +++ b/spec/requests/api/pages/pages_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe API::Pages, feature_category: :pages do - let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) } + let_it_be_with_reload(:project) { create(:project, path: 'my.project', pages_https_only: false) } let_it_be(:admin) { create(:admin) } let_it_be(:user) { create(:user) } @@ -19,7 +19,7 @@ RSpec.describe API::Pages, feature_category: :pages do end it_behaves_like '404 response' do - let(:request) { delete api("/projects/#{project.id}/pages", admin) } + let(:request) { delete api("/projects/#{project.id}/pages", admin, admin_mode: true) } end end @@ -30,13 +30,13 @@ RSpec.describe API::Pages, feature_category: :pages do context 'when Pages are deployed' do it 'returns 204' do - delete api("/projects/#{project.id}/pages", admin) + delete api("/projects/#{project.id}/pages", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:no_content) end it 'removes the pages' do - delete api("/projects/#{project.id}/pages", admin) + delete api("/projects/#{project.id}/pages", admin, admin_mode: true) expect(project.reload.pages_metadatum.deployed?).to be(false) end @@ -48,7 +48,7 @@ RSpec.describe API::Pages, feature_category: :pages do end it 'returns 204' do - delete api("/projects/#{project.id}/pages", admin) + delete api("/projects/#{project.id}/pages", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:no_content) end @@ -58,7 +58,7 @@ RSpec.describe API::Pages, feature_category: :pages do it 'returns 404' do id = -1 - delete api("/projects/#{id}/pages", admin) + delete api("/projects/#{id}/pages", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb index 5cc1b8f9a69..602eff73b0a 100644 --- a/spec/requests/api/pages/private_access_spec.rb +++ b/spec/requests/api/pages/private_access_spec.rb @@ -35,39 +35,39 @@ RSpec.describe "Private Project Pages Access", feature_category: :pages do describe "GET /projects/:id/pages_access" do context 'access depends on the level' do - where(:pages_access_level, :with_user, :expected_result) do - ProjectFeature::DISABLED | "admin" | 403 - ProjectFeature::DISABLED | "owner" | 403 - ProjectFeature::DISABLED | "master" | 403 - ProjectFeature::DISABLED | "developer" | 403 - ProjectFeature::DISABLED | "reporter" | 403 - ProjectFeature::DISABLED | "guest" | 403 - ProjectFeature::DISABLED | "user" | 404 - ProjectFeature::DISABLED | nil | 404 - ProjectFeature::PUBLIC | "admin" | 200 - ProjectFeature::PUBLIC | "owner" | 200 - ProjectFeature::PUBLIC | "master" | 200 - ProjectFeature::PUBLIC | "developer" | 200 - ProjectFeature::PUBLIC | "reporter" | 200 - ProjectFeature::PUBLIC | "guest" | 200 - ProjectFeature::PUBLIC | "user" | 404 - ProjectFeature::PUBLIC | nil | 404 - ProjectFeature::ENABLED | "admin" | 200 - ProjectFeature::ENABLED | "owner" | 200 - ProjectFeature::ENABLED | "master" | 200 - ProjectFeature::ENABLED | "developer" | 200 - ProjectFeature::ENABLED | "reporter" | 200 - ProjectFeature::ENABLED | "guest" | 200 - ProjectFeature::ENABLED | "user" | 404 - ProjectFeature::ENABLED | nil | 404 - ProjectFeature::PRIVATE | "admin" | 200 - ProjectFeature::PRIVATE | "owner" | 200 - ProjectFeature::PRIVATE | "master" | 200 - ProjectFeature::PRIVATE | "developer" | 200 - ProjectFeature::PRIVATE | "reporter" | 200 - ProjectFeature::PRIVATE | "guest" | 200 - ProjectFeature::PRIVATE | "user" | 404 - ProjectFeature::PRIVATE | nil | 404 + where(:pages_access_level, :with_user, :admin_mode, :expected_result) do + ProjectFeature::DISABLED | "admin" | true | 403 + ProjectFeature::DISABLED | "owner" | false | 403 + ProjectFeature::DISABLED | "master" | false | 403 + ProjectFeature::DISABLED | "developer" | false | 403 + ProjectFeature::DISABLED | "reporter" | false | 403 + ProjectFeature::DISABLED | "guest" | false | 403 + ProjectFeature::DISABLED | "user" | false | 404 + ProjectFeature::DISABLED | nil | false | 404 + ProjectFeature::PUBLIC | "admin" | true | 200 + ProjectFeature::PUBLIC | "owner" | false | 200 + ProjectFeature::PUBLIC | "master" | false | 200 + ProjectFeature::PUBLIC | "developer" | false | 200 + ProjectFeature::PUBLIC | "reporter" | false | 200 + ProjectFeature::PUBLIC | "guest" | false | 200 + ProjectFeature::PUBLIC | "user" | false | 404 + ProjectFeature::PUBLIC | nil | false | 404 + ProjectFeature::ENABLED | "admin" | true | 200 + ProjectFeature::ENABLED | "owner" | false | 200 + ProjectFeature::ENABLED | "master" | false | 200 + ProjectFeature::ENABLED | "developer" | false | 200 + ProjectFeature::ENABLED | "reporter" | false | 200 + ProjectFeature::ENABLED | "guest" | false | 200 + ProjectFeature::ENABLED | "user" | false | 404 + ProjectFeature::ENABLED | nil | false | 404 + ProjectFeature::PRIVATE | "admin" | true | 200 + ProjectFeature::PRIVATE | "owner" | false | 200 + ProjectFeature::PRIVATE | "master" | false | 200 + ProjectFeature::PRIVATE | "developer" | false | 200 + ProjectFeature::PRIVATE | "reporter" | false | 200 + ProjectFeature::PRIVATE | "guest" | false | 200 + ProjectFeature::PRIVATE | "user" | false | 404 + ProjectFeature::PRIVATE | nil | false | 404 end with_them do @@ -77,7 +77,7 @@ RSpec.describe "Private Project Pages Access", feature_category: :pages do it "correct return value" do if !with_user.nil? user = public_send(with_user) - get api("/projects/#{project.id}/pages_access", user) + get api("/projects/#{project.id}/pages_access", user, admin_mode: admin_mode) else get api("/projects/#{project.id}/pages_access") end diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb index 1137f91f4b0..8b0ed7c59ab 100644 --- a/spec/requests/api/pages/public_access_spec.rb +++ b/spec/requests/api/pages/public_access_spec.rb @@ -35,39 +35,39 @@ RSpec.describe "Public Project Pages Access", feature_category: :pages do describe "GET /projects/:id/pages_access" do context 'access depends on the level' do - where(:pages_access_level, :with_user, :expected_result) do - ProjectFeature::DISABLED | "admin" | 403 - ProjectFeature::DISABLED | "owner" | 403 - ProjectFeature::DISABLED | "master" | 403 - ProjectFeature::DISABLED | "developer" | 403 - ProjectFeature::DISABLED | "reporter" | 403 - ProjectFeature::DISABLED | "guest" | 403 - ProjectFeature::DISABLED | "user" | 403 - ProjectFeature::DISABLED | nil | 403 - ProjectFeature::PUBLIC | "admin" | 200 - ProjectFeature::PUBLIC | "owner" | 200 - ProjectFeature::PUBLIC | "master" | 200 - ProjectFeature::PUBLIC | "developer" | 200 - ProjectFeature::PUBLIC | "reporter" | 200 - ProjectFeature::PUBLIC | "guest" | 200 - ProjectFeature::PUBLIC | "user" | 200 - ProjectFeature::PUBLIC | nil | 200 - ProjectFeature::ENABLED | "admin" | 200 - ProjectFeature::ENABLED | "owner" | 200 - ProjectFeature::ENABLED | "master" | 200 - ProjectFeature::ENABLED | "developer" | 200 - ProjectFeature::ENABLED | "reporter" | 200 - ProjectFeature::ENABLED | "guest" | 200 - ProjectFeature::ENABLED | "user" | 200 - ProjectFeature::ENABLED | nil | 200 - ProjectFeature::PRIVATE | "admin" | 200 - ProjectFeature::PRIVATE | "owner" | 200 - ProjectFeature::PRIVATE | "master" | 200 - ProjectFeature::PRIVATE | "developer" | 200 - ProjectFeature::PRIVATE | "reporter" | 200 - ProjectFeature::PRIVATE | "guest" | 200 - ProjectFeature::PRIVATE | "user" | 403 - ProjectFeature::PRIVATE | nil | 403 + where(:pages_access_level, :with_user, :admin_mode, :expected_result) do + ProjectFeature::DISABLED | "admin" | false | 403 + ProjectFeature::DISABLED | "owner" | false | 403 + ProjectFeature::DISABLED | "master" | false | 403 + ProjectFeature::DISABLED | "developer" | false | 403 + ProjectFeature::DISABLED | "reporter" | false | 403 + ProjectFeature::DISABLED | "guest" | false | 403 + ProjectFeature::DISABLED | "user" | false | 403 + ProjectFeature::DISABLED | nil | false | 403 + ProjectFeature::PUBLIC | "admin" | false | 200 + ProjectFeature::PUBLIC | "owner" | false | 200 + ProjectFeature::PUBLIC | "master" | false | 200 + ProjectFeature::PUBLIC | "developer" | false | 200 + ProjectFeature::PUBLIC | "reporter" | false | 200 + ProjectFeature::PUBLIC | "guest" | false | 200 + ProjectFeature::PUBLIC | "user" | false | 200 + ProjectFeature::PUBLIC | nil | false | 200 + ProjectFeature::ENABLED | "admin" | false | 200 + ProjectFeature::ENABLED | "owner" | false | 200 + ProjectFeature::ENABLED | "master" | false | 200 + ProjectFeature::ENABLED | "developer" | false | 200 + ProjectFeature::ENABLED | "reporter" | false | 200 + ProjectFeature::ENABLED | "guest" | false | 200 + ProjectFeature::ENABLED | "user" | false | 200 + ProjectFeature::ENABLED | nil | false | 200 + ProjectFeature::PRIVATE | "admin" | true | 200 + ProjectFeature::PRIVATE | "owner" | false | 200 + ProjectFeature::PRIVATE | "master" | false | 200 + ProjectFeature::PRIVATE | "developer" | false | 200 + ProjectFeature::PRIVATE | "reporter" | false | 200 + ProjectFeature::PRIVATE | "guest" | false | 200 + ProjectFeature::PRIVATE | "user" | false | 403 + ProjectFeature::PRIVATE | nil | false | 403 end with_them do @@ -77,7 +77,7 @@ RSpec.describe "Public Project Pages Access", feature_category: :pages do it "correct return value" do if !with_user.nil? user = public_send(with_user) - get api("/projects/#{project.id}/pages_access", user) + get api("/projects/#{project.id}/pages_access", user, admin_mode: admin_mode) else get api("/projects/#{project.id}/pages_access") end diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb index ba1fb5105b8..ea83fa384af 100644 --- a/spec/requests/api/pages_domains_spec.rb +++ b/spec/requests/api/pages_domains_spec.rb @@ -41,14 +41,14 @@ RSpec.describe API::PagesDomains, feature_category: :pages do end it_behaves_like '404 response' do - let(:request) { get api('/pages/domains', admin) } + let(:request) { get api('/pages/domains', admin, admin_mode: true) } end end context 'when pages is enabled' do context 'when authenticated as an admin' do - it 'returns paginated all pages domains' do - get api('/pages/domains', admin) + it 'returns paginated all pages domains', :aggregate_failures do + get api('/pages/domains', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('public_api/v4/pages_domain_basics') @@ -74,7 +74,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do describe 'GET /projects/:project_id/pages/domains' do shared_examples_for 'get pages domains' do - it 'returns paginated pages domains' do + it 'returns paginated pages domains', :aggregate_failures do get api(route, user) expect(response).to have_gitlab_http_status(:ok) @@ -145,7 +145,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do describe 'GET /projects/:project_id/pages/domains/:domain' do shared_examples_for 'get pages domain' do - it 'returns pages domain' do + it 'returns pages domain', :aggregate_failures do get api(route_domain, user) expect(response).to have_gitlab_http_status(:ok) @@ -155,7 +155,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(json_response['certificate']).to be_nil end - it 'returns pages domain with project path' do + it 'returns pages domain with project path', :aggregate_failures do get api(route_domain_path, user) expect(response).to have_gitlab_http_status(:ok) @@ -165,7 +165,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(json_response['certificate']).to be_nil end - it 'returns pages domain with a certificate' do + it 'returns pages domain with a certificate', :aggregate_failures do get api(route_secure_domain, user) expect(response).to have_gitlab_http_status(:ok) @@ -177,7 +177,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(json_response['auto_ssl_enabled']).to be false end - it 'returns pages domain with an expired certificate' do + it 'returns pages domain with an expired certificate', :aggregate_failures do get api(route_expired_domain, user) expect(response).to have_gitlab_http_status(:ok) @@ -185,7 +185,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(json_response['certificate']['expired']).to be true end - it 'returns pages domain with letsencrypt' do + it 'returns pages domain with letsencrypt', :aggregate_failures do get api(route_letsencrypt_domain, user) expect(response).to have_gitlab_http_status(:ok) @@ -258,7 +258,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do let(:params_secure) { pages_domain_secure_params.slice(:domain, :certificate, :key) } shared_examples_for 'post pages domains' do - it 'creates a new pages domain' do + it 'creates a new pages domain', :aggregate_failures do expect { post api(route, user), params: params } .to publish_event(PagesDomains::PagesDomainCreatedEvent) .with( @@ -279,7 +279,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(pages_domain.auto_ssl_enabled).to be false end - it 'creates a new secure pages domain' do + it 'creates a new secure pages domain', :aggregate_failures do post api(route, user), params: params_secure pages_domain = PagesDomain.find_by(domain: json_response['domain']) @@ -291,7 +291,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(pages_domain.auto_ssl_enabled).to be false end - it 'creates domain with letsencrypt enabled' do + it 'creates domain with letsencrypt enabled', :aggregate_failures do post api(route, user), params: pages_domain_with_letsencrypt_params pages_domain = PagesDomain.find_by(domain: json_response['domain']) @@ -301,7 +301,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(pages_domain.auto_ssl_enabled).to be true end - it 'creates domain with letsencrypt enabled and provided certificate' do + it 'creates domain with letsencrypt enabled and provided certificate', :aggregate_failures do post api(route, user), params: params_secure.merge(auto_ssl_enabled: true) pages_domain = PagesDomain.find_by(domain: json_response['domain']) @@ -376,7 +376,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do let(:params_secure_nokey) { pages_domain_secure_params.slice(:certificate) } shared_examples_for 'put pages domain' do - it 'updates pages domain removing certificate' do + it 'updates pages domain removing certificate', :aggregate_failures do put api(route_secure_domain, user), params: { certificate: nil, key: nil } pages_domain_secure.reload @@ -399,7 +399,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do ) end - it 'updates pages domain adding certificate' do + it 'updates pages domain adding certificate', :aggregate_failures do put api(route_domain, user), params: params_secure pages_domain.reload @@ -409,7 +409,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(pages_domain.key).to eq(params_secure[:key]) end - it 'updates pages domain adding certificate with letsencrypt' do + it 'updates pages domain adding certificate with letsencrypt', :aggregate_failures do put api(route_domain, user), params: params_secure.merge(auto_ssl_enabled: true) pages_domain.reload @@ -420,7 +420,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(pages_domain.auto_ssl_enabled).to be true end - it 'updates pages domain enabling letsencrypt' do + it 'updates pages domain enabling letsencrypt', :aggregate_failures do put api(route_domain, user), params: { auto_ssl_enabled: true } pages_domain.reload @@ -429,7 +429,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(pages_domain.auto_ssl_enabled).to be true end - it 'updates pages domain disabling letsencrypt while preserving the certificate' do + it 'updates pages domain disabling letsencrypt while preserving the certificate', :aggregate_failures do put api(route_letsencrypt_domain, user), params: { auto_ssl_enabled: false } pages_domain_with_letsencrypt.reload @@ -440,7 +440,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(pages_domain_with_letsencrypt.certificate).to be end - it 'updates pages domain with expired certificate' do + it 'updates pages domain with expired certificate', :aggregate_failures do put api(route_expired_domain, user), params: params_secure pages_domain_expired.reload @@ -450,7 +450,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do expect(pages_domain_expired.key).to eq(params_secure[:key]) end - it 'updates pages domain with expired certificate not updating key' do + it 'updates pages domain with expired certificate not updating key', :aggregate_failures do put api(route_secure_domain, user), params: params_secure_nokey pages_domain_secure.reload diff --git a/spec/requests/api/personal_access_tokens/self_information_spec.rb b/spec/requests/api/personal_access_tokens/self_information_spec.rb index 4a3c0ad8904..2a7af350054 100644 --- a/spec/requests/api/personal_access_tokens/self_information_spec.rb +++ b/spec/requests/api/personal_access_tokens/self_information_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::PersonalAccessTokens::SelfInformation, feature_category: :authentication_and_authorization do +RSpec.describe API::PersonalAccessTokens::SelfInformation, feature_category: :system_access do let(:path) { '/personal_access_tokens/self' } let(:token) { create(:personal_access_token, user: current_user) } diff --git a/spec/requests/api/personal_access_tokens_spec.rb b/spec/requests/api/personal_access_tokens_spec.rb index 32adc7ebd61..cca94c7a012 100644 --- a/spec/requests/api/personal_access_tokens_spec.rb +++ b/spec/requests/api/personal_access_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::PersonalAccessTokens, feature_category: :authentication_and_authorization do +RSpec.describe API::PersonalAccessTokens, feature_category: :system_access do let_it_be(:path) { '/personal_access_tokens' } describe 'GET /personal_access_tokens' do diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index 9d722e4a445..978ac28ef73 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -6,8 +6,12 @@ RSpec.describe API::ProjectMilestones, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be_with_reload(:project) { create(:project, namespace: user.namespace) } let_it_be(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } - let_it_be(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } let_it_be(:route) { "/projects/#{project.id}/milestones" } + let_it_be(:milestone) do + create(:milestone, project: project, title: 'version2', description: 'open milestone', updated_at: 5.days.ago) + end + + let(:params) { {} } before_all do project.add_reporter(user) @@ -15,38 +19,43 @@ RSpec.describe API::ProjectMilestones, feature_category: :team_planning do it_behaves_like 'group and project milestones', "/projects/:id/milestones" + shared_examples 'listing all milestones' do + it 'returns correct list of milestones' do + get api(route, user), params: params + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.size).to eq(milestones.size) + expect(json_response.map { |entry| entry["id"] }).to match_array(milestones.map(&:id)) + end + end + describe 'GET /projects/:id/milestones' do - context 'when include_parent_milestones is true' do - let_it_be(:ancestor_group) { create(:group, :private) } - let_it_be(:group) { create(:group, :private, parent: ancestor_group) } - let_it_be(:ancestor_group_milestone) { create(:milestone, group: ancestor_group) } - let_it_be(:group_milestone) { create(:milestone, group: group) } + let_it_be(:ancestor_group) { create(:group, :private) } + let_it_be(:group) { create(:group, :private, parent: ancestor_group) } + let_it_be(:ancestor_group_milestone) { create(:milestone, group: ancestor_group, updated_at: 1.day.ago) } + let_it_be(:group_milestone) { create(:milestone, group: group, updated_at: 3.days.ago) } - let(:params) { { include_parent_milestones: true } } + context 'when project parent is a namespace' do + let(:milestones) { [milestone, closed_milestone] } - shared_examples 'listing all milestones' do - it 'returns correct list of milestones' do - get api(route, user), params: params + it_behaves_like 'listing all milestones' - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.size).to eq(milestones.size) - expect(json_response.map { |entry| entry["id"] }).to eq(milestones.map(&:id)) - end + context 'when include_parent_milestones is true' do + let(:params) { { include_parent_milestones: true } } + + it_behaves_like 'listing all milestones' end + end - context 'when project parent is a namespace' do - it_behaves_like 'listing all milestones' do - let(:milestones) { [milestone, closed_milestone] } - end + context 'when project parent is a group' do + before_all do + project.update!(namespace: group) end - context 'when project parent is a group' do + context 'when include_parent_milestones is true' do + let(:params) { { include_parent_milestones: true } } let(:milestones) { [group_milestone, ancestor_group_milestone, milestone, closed_milestone] } - before_all do - project.update!(namespace: group) - end - it_behaves_like 'listing all milestones' context 'when iids param is present' do @@ -64,6 +73,38 @@ RSpec.describe API::ProjectMilestones, feature_category: :team_planning do expect(response).to have_gitlab_http_status(:not_found) end end + + context 'when updated_before param is present' do + let(:params) { { updated_before: 12.hours.ago.iso8601, include_parent_milestones: true } } + + it_behaves_like 'listing all milestones' do + let(:milestones) { [group_milestone, ancestor_group_milestone, milestone] } + end + end + + context 'when updated_after param is present' do + let(:params) { { updated_after: 2.days.ago.iso8601, include_parent_milestones: true } } + + it_behaves_like 'listing all milestones' do + let(:milestones) { [ancestor_group_milestone, closed_milestone] } + end + end + end + + context 'when updated_before param is present' do + let(:params) { { updated_before: 12.hours.ago.iso8601 } } + + it_behaves_like 'listing all milestones' do + let(:milestones) { [milestone] } + end + end + + context 'when updated_after param is present' do + let(:params) { { updated_after: 2.days.ago.iso8601 } } + + it_behaves_like 'listing all milestones' do + let(:milestones) { [closed_milestone] } + end end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e78ef2f7630..d755a4231da 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1109,6 +1109,66 @@ RSpec.describe API::Projects, feature_category: :projects do end.not_to exceed_query_limit(control) end end + + context 'rate limiting' do + let_it_be(:current_user) { create(:user) } + + shared_examples_for 'does not log request and does not block the request' do + specify do + request + request + + expect(response).not_to have_gitlab_http_status(:too_many_requests) + expect(Gitlab::AuthLogger).not_to receive(:error) + end + end + + before do + stub_application_setting(projects_api_rate_limit_unauthenticated: 1) + end + + context 'when the user is signed in' do + it_behaves_like 'does not log request and does not block the request' do + def request + get api('/projects', current_user) + end + end + end + + context 'when the user is not signed in' do + let_it_be(:current_user) { nil } + + it_behaves_like 'rate limited endpoint', rate_limit_key: :projects_api_rate_limit_unauthenticated do + def request + get api('/projects', current_user) + end + end + end + + context 'when the feature flag `rate_limit_for_unauthenticated_projects_api_access` is disabled' do + before do + stub_feature_flags(rate_limit_for_unauthenticated_projects_api_access: false) + end + + context 'when the user is not signed in' do + let_it_be(:current_user) { nil } + + it_behaves_like 'does not log request and does not block the request' do + def request + get api('/projects', current_user) + end + end + end + + context 'when the user is signed in' do + it_behaves_like 'does not log request and does not block the request' do + def request + get api('/projects', current_user) + end + end + end + end + end end describe 'POST /projects' do @@ -2006,19 +2066,6 @@ RSpec.describe API::Projects, feature_category: :projects do end end - context 'with upload size enforcement disabled' do - before do - stub_feature_flags(enforce_max_attachment_size_upload_api: false) - end - - it "returns 200" do - post api("/projects/#{project.id}/uploads/authorize", user), headers: headers - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['MaximumSize']).to eq(1.gigabyte) - end - end - context 'with no Workhorse headers' do it "returns 403" do post api("/projects/#{project.id}/uploads/authorize", user) @@ -2095,14 +2142,6 @@ RSpec.describe API::Projects, feature_category: :projects do it_behaves_like 'capped upload attachments', true end - - context 'with upload size enforcement disabled' do - before do - stub_feature_flags(enforce_max_attachment_size_upload_api: false) - end - - it_behaves_like 'capped upload attachments', false - end end describe "GET /projects/:id/groups" do @@ -2228,9 +2267,9 @@ RSpec.describe API::Projects, feature_category: :projects do end describe 'GET /project/:id/share_locations' do - let_it_be(:root_group) { create(:group, :public, name: 'root group') } - let_it_be(:project_group1) { create(:group, :public, parent: root_group, name: 'group1') } - let_it_be(:project_group2) { create(:group, :public, parent: root_group, name: 'group2') } + let_it_be(:root_group) { create(:group, :public, name: 'root group', path: 'root-group-path') } + let_it_be(:project_group1) { create(:group, :public, parent: root_group, name: 'group1', path: 'group-1-path') } + let_it_be(:project_group2) { create(:group, :public, parent: root_group, name: 'group2', path: 'group-2-path') } let_it_be(:project) { create(:project, :private, group: project_group1) } shared_examples_for 'successful groups response' do @@ -2280,10 +2319,22 @@ RSpec.describe API::Projects, feature_category: :projects do end context 'when searching by group name' do - let(:params) { { search: 'group1' } } + context 'searching by group name' do + it_behaves_like 'successful groups response' do + let(:params) { { search: 'group1' } } + let(:expected_groups) { [project_group1] } + end + end - it_behaves_like 'successful groups response' do - let(:expected_groups) { [project_group1] } + context 'searching by full group path' do + let_it_be(:project_group2_subgroup) do + create(:group, :public, parent: project_group2, name: 'subgroup', path: 'subgroup-path') + end + + it_behaves_like 'successful groups response' do + let(:params) { { search: 'root-group-path/group-2-path/subgroup-path' } } + let(:expected_groups) { [project_group2_subgroup] } + end end end end diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb index 8e8a25a8dc2..463893afd13 100644 --- a/spec/requests/api/protected_branches_spec.rb +++ b/spec/requests/api/protected_branches_spec.rb @@ -266,6 +266,15 @@ RSpec.describe API::ProtectedBranches, feature_category: :source_code_management end.to change { protected_branch.reload.allow_force_push }.from(false).to(true) expect(response).to have_gitlab_http_status(:ok) end + + context 'when allow_force_push is not set' do + it 'responds with a bad request error' do + patch api(route, user), params: { allow_force_push: nil } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq 'allow_force_push is empty' + end + end end context 'when returned protected branch is invalid' do diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb index 978d4f72a4a..0b2641b062c 100644 --- a/spec/requests/api/pypi_packages_spec.rb +++ b/spec/requests/api/pypi_packages_spec.rb @@ -14,10 +14,18 @@ RSpec.describe API::PypiPackages, feature_category: :package_registry do let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } } + let(:snowplow_gitlab_standard_context) { snowplow_context } let(:headers) { {} } + def snowplow_context(user_role: :developer) + if user_role == :anonymous + { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } + else + { project: project, namespace: project.namespace, property: 'i_package_pypi_user', user: user } + end + end + context 'simple index API endpoint' do let_it_be(:package) { create(:pypi_package, project: project) } let_it_be(:package2) { create(:pypi_package, project: project) } @@ -26,7 +34,6 @@ RSpec.describe API::PypiPackages, feature_category: :package_registry do describe 'GET /api/v4/groups/:id/-/packages/pypi/simple' do let(:url) { "/groups/#{group.id}/-/packages/pypi/simple" } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } } it_behaves_like 'pypi simple index API endpoint' it_behaves_like 'rejects PyPI access with unknown group id' @@ -82,13 +89,13 @@ RSpec.describe API::PypiPackages, feature_category: :package_registry do context 'simple package API endpoint' do let_it_be(:package) { create(:pypi_package, project: project) } - let(:snowplow_gitlab_standard_context) { { project: nil, namespace: group, property: 'i_package_pypi_user' } } subject { get api(url), headers: headers } describe 'GET /api/v4/groups/:id/-/packages/pypi/simple/:package_name' do let(:package_name) { package.name } let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package_name}" } + let(:snowplow_context) { { project: nil, namespace: project.namespace, property: 'i_package_pypi_user' } } it_behaves_like 'pypi simple API endpoint' it_behaves_like 'rejects PyPI access with unknown group id' @@ -126,7 +133,7 @@ RSpec.describe API::PypiPackages, feature_category: :package_registry do describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do let(:package_name) { package.name } let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package_name}" } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } } + let(:snowplow_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } } it_behaves_like 'pypi simple API endpoint' it_behaves_like 'rejects PyPI access with unknown project id' @@ -242,6 +249,13 @@ RSpec.describe API::PypiPackages, feature_category: :package_registry do let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } let(:headers) { user_headers.merge(workhorse_headers) } + let(:snowplow_gitlab_standard_context) do + if user_role == :anonymous || (visibility_level == :public && !user_token) + { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } + else + { project: project, namespace: project.namespace, property: 'i_package_pypi_user', user: user } + end + end before do project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) @@ -379,6 +393,14 @@ RSpec.describe API::PypiPackages, feature_category: :package_registry do let_it_be(:package_name) { 'Dummy-Package' } let_it_be(:package) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') } + let(:snowplow_gitlab_standard_context) do + if user_role == :anonymous || (visibility_level == :public && !user_token) + { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } + else + { project: project, namespace: project.namespace, property: 'i_package_pypi_user', user: user } + end + end + subject { get api(url), headers: headers } describe 'GET /api/v4/groups/:id/-/packages/pypi/files/:sha256/*file_identifier' do diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb index 462cc1e3b5d..4b388304621 100644 --- a/spec/requests/api/release/links_spec.rb +++ b/spec/requests/api/release/links_spec.rb @@ -377,6 +377,15 @@ RSpec.describe API::Release::Links, feature_category: :release_orchestration do expect(response).to match_response_schema('release/link') end + context 'when params are invalid' do + it 'returns 400 error' do + put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer), + params: params.merge(url: 'wrong_url') + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + context 'when using `direct_asset_path`' do it 'updates the release link' do put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer), @@ -534,6 +543,21 @@ RSpec.describe API::Release::Links, feature_category: :release_orchestration do end end + context 'when destroy process fails' do + before do + allow_next_instance_of(::Releases::Links::DestroyService) do |service| + allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'error')) + end + end + + it_behaves_like '400 response' do + let(:message) { 'error' } + let(:request) do + delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer) + end + end + end + context 'when there are no corresponding release link' do let!(:release_link) {} diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 555ba2bc978..b146dda5030 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -236,7 +236,6 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do get api(route, current_user) expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate, no-store, no-cache") - expect(response.headers["Pragma"]).to eq("no-cache") expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") end diff --git a/spec/requests/api/resource_access_tokens_spec.rb b/spec/requests/api/resource_access_tokens_spec.rb index 6a89e9a56df..9277fa18219 100644 --- a/spec/requests/api/resource_access_tokens_spec.rb +++ b/spec/requests/api/resource_access_tokens_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe API::ResourceAccessTokens, feature_category: :authentication_and_authorization do +RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do let_it_be(:user) { create(:user) } let_it_be(:user_non_priviledged) { create(:user) } diff --git a/spec/requests/api/rubygem_packages_spec.rb b/spec/requests/api/rubygem_packages_spec.rb index 34cf6033811..1774b43ccb3 100644 --- a/spec/requests/api/rubygem_packages_spec.rb +++ b/spec/requests/api/rubygem_packages_spec.rb @@ -8,6 +8,14 @@ RSpec.describe API::RubygemPackages, feature_category: :package_registry do using RSpec::Parameterized::TableSyntax let_it_be_with_reload(:project) { create(:project) } + let(:tokens) do + { + personal_access_token: personal_access_token.token, + deploy_token: deploy_token.token, + job_token: job.token + } + end + let_it_be(:personal_access_token) { create(:personal_access_token) } let_it_be(:user) { personal_access_token.user } let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } @@ -15,14 +23,14 @@ RSpec.describe API::RubygemPackages, feature_category: :package_registry do let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:headers) { {} } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_rubygems_user' } } + let(:snowplow_gitlab_standard_context) { snowplow_context } - let(:tokens) do - { - personal_access_token: personal_access_token.token, - deploy_token: deploy_token.token, - job_token: job.token - } + def snowplow_context(user_role: :developer) + if user_role == :anonymous + { project: project, namespace: project.namespace, property: 'i_package_rubygems_user' } + else + { project: project, namespace: project.namespace, property: 'i_package_rubygems_user', user: user } + end end shared_examples 'when feature flag is disabled' do @@ -164,7 +172,13 @@ RSpec.describe API::RubygemPackages, feature_category: :package_registry do with_them do let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' } let(:headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_rubygems_user' } } + let(:snowplow_gitlab_standard_context) do + if token_type == :deploy_token + snowplow_context.merge(user: deploy_token) + else + snowplow_context(user_role: user_role) + end + end before do project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s)) diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index 035f53db12e..eb0f3b3eaee 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -174,6 +174,23 @@ RSpec.describe API::Search, feature_category: :global_search do end end + context 'when there is a search error' do + let(:results) { instance_double('Gitlab::SearchResults', failed?: true, error: 'failed to parse query') } + + before do + allow_next_instance_of(SearchService) do |service| + allow(service).to receive(:search_objects).and_return([]) + allow(service).to receive(:search_results).and_return(results) + end + end + + it 'returns 400 error' do + get api(endpoint, user), params: { scope: 'issues', search: 'expected to fail' } + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + context 'with correct params' do context 'for projects scope' do before do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 4d85849cff3..e91d777bfb0 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, feature_category: :not_owned do +RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, feature_category: :shared do let(:user) { create(:user) } let_it_be(:admin) { create(:admin) } @@ -66,6 +66,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu expect(json_response['jira_connect_application_key']).to eq(nil) expect(json_response['jira_connect_proxy_url']).to eq(nil) expect(json_response['user_defaults_to_private_profile']).to eq(false) + expect(json_response['default_syntax_highlighting_theme']).to eq(1) + expect(json_response['projects_api_rate_limit_unauthenticated']).to eq(400) end end @@ -169,7 +171,9 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu jira_connect_proxy_url: 'http://example.com', bulk_import_enabled: false, allow_runner_registration_token: true, - user_defaults_to_private_profile: true + user_defaults_to_private_profile: true, + default_syntax_highlighting_theme: 2, + projects_api_rate_limit_unauthenticated: 100 } expect(response).to have_gitlab_http_status(:ok) @@ -237,6 +241,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu expect(json_response['bulk_import_enabled']).to be(false) expect(json_response['allow_runner_registration_token']).to be(true) expect(json_response['user_defaults_to_private_profile']).to be(true) + expect(json_response['default_syntax_highlighting_theme']).to eq(2) + expect(json_response['projects_api_rate_limit_unauthenticated']).to be(100) end end diff --git a/spec/requests/api/sidekiq_metrics_spec.rb b/spec/requests/api/sidekiq_metrics_spec.rb index 1085df97cc7..32c4c323923 100644 --- a/spec/requests/api/sidekiq_metrics_spec.rb +++ b/spec/requests/api/sidekiq_metrics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::SidekiqMetrics, feature_category: :not_owned do +RSpec.describe API::SidekiqMetrics, feature_category: :shared do let(:admin) { create(:user, :admin) } describe 'GET sidekiq/*' do diff --git a/spec/requests/api/terraform/modules/v1/packages_spec.rb b/spec/requests/api/terraform/modules/v1/packages_spec.rb index 2bd7cb027aa..f479ca25f3c 100644 --- a/spec/requests/api/terraform/modules/v1/packages_spec.rb +++ b/spec/requests/api/terraform/modules/v1/packages_spec.rb @@ -415,12 +415,15 @@ RSpec.describe API::Terraform::Modules::V1::Packages, feature_category: :package with_them do let(:snowplow_gitlab_standard_context) do - { + context = { project: project, - user: user_role == :anonymous ? nil : user, namespace: project.namespace, property: 'i_package_terraform_module_user' } + + context[:user] = user if user_role != :anonymous + + context end before do diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb index fd34345d814..c94643242c9 100644 --- a/spec/requests/api/terraform/state_spec.rb +++ b/spec/requests/api/terraform/state_spec.rb @@ -21,6 +21,7 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu before do stub_terraform_state_object_storage + stub_config(terraform_state: { enabled: true }) end shared_examples 'endpoint with unique user tracking' do @@ -51,7 +52,6 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu it_behaves_like 'Snowplow event tracking with RedisHLL context' do subject(:api_request) { request } - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:category) { described_class.name } let(:action) { 'terraform_state_api_request' } let(:label) { 'redis_hll_counters.terraform.p_terraform_state_api_unique_users_monthly' } @@ -82,6 +82,7 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu subject(:request) { get api(state_path), headers: auth_header } it_behaves_like 'endpoint with unique user tracking' + it_behaves_like 'it depends on value of the `terraform_state.enabled` config' context 'without authentication' do let(:auth_header) { basic_auth_header('bad', 'token') } @@ -194,6 +195,7 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu subject(:request) { post api(state_path), headers: auth_header, as: :json, params: params } it_behaves_like 'endpoint with unique user tracking' + it_behaves_like 'it depends on value of the `terraform_state.enabled` config' context 'when terraform state with a given name is already present' do context 'with maintainer permissions' do @@ -372,6 +374,7 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu subject(:request) { delete api(state_path), headers: auth_header } it_behaves_like 'endpoint with unique user tracking' + it_behaves_like 'it depends on value of the `terraform_state.enabled` config' shared_examples 'schedules the state for deletion' do it 'returns empty body' do @@ -553,6 +556,10 @@ RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructu let(:lock_id) { 'irrelevant to this test, just needs to be present' } end + it_behaves_like 'it depends on value of the `terraform_state.enabled` config' do + let(:lock_id) { '123.456' } + end + where(given_state_name: %w[test-state test.state test%2Ffoo]) with_them do let(:state_name) { given_state_name } diff --git a/spec/requests/api/terraform/state_version_spec.rb b/spec/requests/api/terraform/state_version_spec.rb index 28abbb5749d..24b3ca94581 100644 --- a/spec/requests/api/terraform/state_version_spec.rb +++ b/spec/requests/api/terraform/state_version_spec.rb @@ -22,9 +22,15 @@ RSpec.describe API::Terraform::StateVersion, feature_category: :infrastructure_a let(:version_serial) { version.version } let(:state_version_path) { "/projects/#{project_id}/terraform/state/#{state_name}/versions/#{version_serial}" } + before do + stub_config(terraform_state: { enabled: true }) + end + describe 'GET /projects/:id/terraform/state/:name/versions/:serial' do subject(:request) { get api(state_version_path), headers: auth_header } + it_behaves_like 'it depends on value of the `terraform_state.enabled` config' + context 'with invalid authentication' do let(:auth_header) { basic_auth_header('bad', 'token') } @@ -147,6 +153,8 @@ RSpec.describe API::Terraform::StateVersion, feature_category: :infrastructure_a describe 'DELETE /projects/:id/terraform/state/:name/versions/:serial' do subject(:request) { delete api(state_version_path), headers: auth_header } + it_behaves_like 'it depends on value of the `terraform_state.enabled` config', { success_status: :no_content } + context 'with invalid authentication' do let(:auth_header) { basic_auth_header('bad', 'token') } diff --git a/spec/requests/api/unleash_spec.rb b/spec/requests/api/unleash_spec.rb index 5daf7cd7b75..75b26b98228 100644 --- a/spec/requests/api/unleash_spec.rb +++ b/spec/requests/api/unleash_spec.rb @@ -88,6 +88,14 @@ RSpec.describe API::Unleash, feature_category: :feature_flags do end end + describe 'GET /feature_flags/unleash/:project_id/client/features', :use_clean_rails_redis_caching do + specify do + get api("/feature_flags/unleash/#{project_id}/client/features"), params: params, headers: headers + + is_expected.to have_request_urgency(:medium) + end + end + %w(/feature_flags/unleash/:project_id/features /feature_flags/unleash/:project_id/client/features).each do |features_endpoint| describe "GET #{features_endpoint}", :use_clean_rails_redis_caching do let(:features_url) { features_endpoint.sub(':project_id', project_id.to_s) } diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 34867b13db2..c924f529e11 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1288,7 +1288,7 @@ RSpec.describe API::Users, feature_category: :user_profile do expect(json_response['message']['projects_limit']) .to eq(['must be greater than or equal to 0']) expect(json_response['message']['username']) - .to eq([Gitlab::PathRegex.namespace_format_message]) + .to match_array([Gitlab::PathRegex.namespace_format_message, Gitlab::Regex.oci_repository_path_regex_message]) end it 'tracks weak password errors' do @@ -1823,7 +1823,7 @@ RSpec.describe API::Users, feature_category: :user_profile do expect(json_response['message']['projects_limit']) .to eq(['must be greater than or equal to 0']) expect(json_response['message']['username']) - .to eq([Gitlab::PathRegex.namespace_format_message]) + .to match_array([Gitlab::PathRegex.namespace_format_message, Gitlab::Regex.oci_repository_path_regex_message]) end it 'returns 400 if provider is missing for identity update' do @@ -4150,7 +4150,7 @@ RSpec.describe API::Users, feature_category: :user_profile do set_user_status expect(response).to have_gitlab_http_status(:success) - expect(user_with_status.status).to be_nil + expect(user_with_status.reset.status).to be_nil end end end @@ -4178,7 +4178,7 @@ RSpec.describe API::Users, feature_category: :user_profile do set_user_status expect(response).to have_gitlab_http_status(:success) - expect(user_with_status.status.clear_status_at).to be_nil + expect(user_with_status.reset.status.clear_status_at).to be_nil end end @@ -4217,7 +4217,7 @@ RSpec.describe API::Users, feature_category: :user_profile do set_user_status expect(response).to have_gitlab_http_status(:success) - expect(user_with_status.status).to be_nil + expect(user_with_status.reset.status).to be_nil end end @@ -4229,7 +4229,7 @@ RSpec.describe API::Users, feature_category: :user_profile do set_user_status expect(response).to have_gitlab_http_status(:success) - expect(user_with_status.status.clear_status_at).to be_nil + expect(user_with_status.reset.status.clear_status_at).to be_nil end end end |