diff options
Diffstat (limited to 'spec/requests')
79 files changed, 1829 insertions, 1870 deletions
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index a38ba782c44..36fbe86ac76 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -8,8 +8,8 @@ RSpec.describe API::Branches do let(:guest) { create(:user).tap { |u| project.add_guest(u) } } let(:branch_name) { 'feature' } let(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } - let(:branch_with_dot) { project.repository.find_branch('ends-with.json') } - let(:branch_with_slash) { project.repository.find_branch('improve/awesome') } + let(:branch_with_dot) { 'ends-with.json' } + let(:branch_with_slash) { 'improve/awesome' } let(:project_id) { project.id } let(:current_user) { nil } @@ -105,7 +105,7 @@ RSpec.describe API::Branches do expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('public_api/v4/branches') - expect(response.headers).not_to include('Link', 'Links') + expect(response.headers).not_to include('Link') branch_names = json_response.map { |x| x['name'] } expect(branch_names).to match_array(project.repository.branch_names) end @@ -116,7 +116,7 @@ RSpec.describe API::Branches do get api(route, current_user), params: base_params.merge(per_page: 2) expect(response).to have_gitlab_http_status(:ok) - expect(response.headers).to include('Link', 'Links') + expect(response.headers).to include('Link') expect(json_response.count).to eq 2 check_merge_status(json_response) @@ -285,6 +285,13 @@ RSpec.describe API::Branches do let(:request) { get api(route, current_user) } end end + + context 'when repository does not exist' do + it_behaves_like '404 response' do + let(:project) { create(:project, creator: user) } + let(:request) { get api(route, current_user) } + end + end end context 'when unauthenticated', 'and project is public' do @@ -320,19 +327,19 @@ RSpec.describe API::Branches do end context 'when branch contains a dot' do - let(:branch_name) { branch_with_dot.name } + let(:branch_name) { branch_with_dot } it_behaves_like 'repository branch' end context 'when branch contains dot txt' do - let(:branch_name) { project.repository.find_branch('ends-with.txt').name } + let(:branch_name) { 'ends-with.txt' } it_behaves_like 'repository branch' end context 'when branch contains a slash' do - let(:branch_name) { branch_with_slash.name } + let(:branch_name) { branch_with_slash } it_behaves_like '404 response' do let(:request) { get api(route, current_user) } @@ -340,7 +347,7 @@ RSpec.describe API::Branches do end context 'when branch contains an escaped slash' do - let(:branch_name) { CGI.escape(branch_with_slash.name) } + let(:branch_name) { CGI.escape(branch_with_slash) } it_behaves_like 'repository branch' end @@ -351,7 +358,7 @@ RSpec.describe API::Branches do it_behaves_like 'repository branch' context 'when branch contains a dot' do - let(:branch_name) { branch_with_dot.name } + let(:branch_name) { branch_with_dot } it_behaves_like 'repository branch' end @@ -475,13 +482,13 @@ RSpec.describe API::Branches do it_behaves_like 'repository new protected branch' context 'when branch contains a dot' do - let(:branch_name) { branch_with_dot.name } + let(:branch_name) { branch_with_dot } it_behaves_like 'repository new protected branch' end context 'when branch contains a slash' do - let(:branch_name) { branch_with_slash.name } + let(:branch_name) { branch_with_slash } it_behaves_like '404 response' do let(:request) { put api(route, current_user) } @@ -489,7 +496,7 @@ RSpec.describe API::Branches do end context 'when branch contains an escaped slash' do - let(:branch_name) { CGI.escape(branch_with_slash.name) } + let(:branch_name) { CGI.escape(branch_with_slash) } it_behaves_like 'repository new protected branch' end @@ -500,7 +507,7 @@ RSpec.describe API::Branches do it_behaves_like 'repository new protected branch' context 'when branch contains a dot' do - let(:branch_name) { branch_with_dot.name } + let(:branch_name) { branch_with_dot } it_behaves_like 'repository new protected branch' end @@ -609,13 +616,13 @@ RSpec.describe API::Branches do it_behaves_like 'repository unprotected branch' context 'when branch contains a dot' do - let(:branch_name) { branch_with_dot.name } + let(:branch_name) { branch_with_dot } it_behaves_like 'repository unprotected branch' end context 'when branch contains a slash' do - let(:branch_name) { branch_with_slash.name } + let(:branch_name) { branch_with_slash } it_behaves_like '404 response' do let(:request) { put api(route, current_user) } @@ -623,7 +630,7 @@ RSpec.describe API::Branches do end context 'when branch contains an escaped slash' do - let(:branch_name) { CGI.escape(branch_with_slash.name) } + let(:branch_name) { CGI.escape(branch_with_slash) } it_behaves_like 'repository unprotected branch' end @@ -634,7 +641,7 @@ RSpec.describe API::Branches do it_behaves_like 'repository unprotected branch' context 'when branch contains a dot' do - let(:branch_name) { branch_with_dot.name } + let(:branch_name) { branch_with_dot } it_behaves_like 'repository unprotected branch' end @@ -732,7 +739,7 @@ RSpec.describe API::Branches do end it 'removes a branch with dots in the branch name' do - delete api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}", user) + delete api("/projects/#{project.id}/repository/branches/#{branch_with_dot}", user) expect(response).to have_gitlab_http_status(:no_content) end diff --git a/spec/requests/api/ci/runner/jobs_put_spec.rb b/spec/requests/api/ci/runner/jobs_put_spec.rb index 3d5021fba08..8c95748aa5f 100644 --- a/spec/requests/api/ci/runner/jobs_put_spec.rb +++ b/spec/requests/api/ci/runner/jobs_put_spec.rb @@ -17,18 +17,14 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end describe '/api/v4/jobs' do - let(:group) { create(:group, :nested) } - let(:project) { create(:project, namespace: group, shared_runners_enabled: false) } - let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') } - let(:runner) { create(:ci_runner, :project, projects: [project]) } - let(:user) { create(:user) } - let(:job) do - create(:ci_build, :artifacts, :extended_options, - pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) - end + let_it_be(:group) { create(:group, :nested) } + let_it_be(:project) { create(:project, namespace: group, shared_runners_enabled: false) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') } + let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) } + let_it_be(:user) { create(:user) } describe 'PUT /api/v4/jobs/:id' do - let(:job) do + let_it_be_with_reload(:job) do create(:ci_build, :pending, :trace_live, pipeline: pipeline, project: project, user: user, runner_id: runner.id) end @@ -204,53 +200,6 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end end - context 'when trace is given' do - it 'creates a trace artifact' do - allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do - ArchiveTraceWorker.new.perform(job.id) - end - - update_job(state: 'success', trace: 'BUILD TRACE UPDATED') - - job.reload - expect(response).to have_gitlab_http_status(:ok) - expect(job.trace.raw).to eq 'BUILD TRACE UPDATED' - expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED' - end - - context 'when concurrent update of trace is happening' do - before do - job.trace.write('wb') do - update_job(state: 'success', trace: 'BUILD TRACE UPDATED') - end - end - - it 'returns that operation conflicts' do - expect(response).to have_gitlab_http_status(:conflict) - end - end - end - - context 'when no trace is given' do - it 'does not override trace information' do - update_job - - expect(job.reload.trace.raw).to eq 'BUILD TRACE' - end - - context 'when running state is sent' do - it 'updates update_at value' do - expect { update_job_after_time }.to change { job.reload.updated_at } - end - end - - context 'when other state is sent' do - it "doesn't update update_at value" do - expect { update_job_after_time(20.minutes, state: 'success') }.not_to change { job.reload.updated_at } - end - end - end - context 'when job has been erased' do let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) } @@ -267,20 +216,19 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do job.drop!(:script_failure) end - it 'does not update job status and job trace' do - update_job(state: 'success', trace: 'BUILD TRACE UPDATED') + it 'does not update job status' do + update_job(state: 'success') job.reload expect(response).to have_gitlab_http_status(:forbidden) expect(response.header['Job-Status']).to eq 'failed' - expect(job.trace.raw).to eq 'Job failed' expect(job).to be_failed end end context 'when job does not exist anymore' do it 'returns 403 Forbidden' do - update_job(non_existing_record_id, state: 'success', trace: 'BUILD TRACE UPDATED') + update_job(non_existing_record_id, state: 'success') expect(response).to have_gitlab_http_status(:forbidden) end 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 63da3340a45..8896bd44077 100644 --- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb +++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb @@ -23,7 +23,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do let(:runner) { create(:ci_runner, :project, projects: [project]) } let(:user) { create(:user) } let(:job) do - create(:ci_build, :artifacts, :extended_options, + create(:ci_build, :pending, :queued, :artifacts, :extended_options, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) end @@ -129,7 +129,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do context 'when other projects have pending jobs' do before do job.success - create(:ci_build, :pending) + create(:ci_build, :pending, :queued) end it_behaves_like 'no jobs available' @@ -239,7 +239,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end context 'when job is made for tag' do - let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job) { create(:ci_build, :pending, :queued, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } it 'sets branch as ref_type' do request_job @@ -297,7 +297,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end context 'when job filtered by job_age' do - let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, queued_at: 60.seconds.ago) } + let!(:job) { create(:ci_build, :pending, :queued, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, queued_at: 60.seconds.ago) } context 'job is queued less than job_age parameter' do let(:job_age) { 120 } @@ -359,7 +359,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end context 'when job is for a release' do - let!(:job) { create(:ci_build, :release_options, pipeline: pipeline) } + let!(:job) { create(:ci_build, :pending, :queued, :release_options, pipeline: pipeline) } context 'when `multi_build_steps` is passed by the runner' do it 'exposes release info' do @@ -398,7 +398,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do context 'when job is made for merge request' do let(:pipeline) { create(:ci_pipeline, source: :merge_request_event, project: project, ref: 'feature', merge_request: merge_request) } - let!(:job) { create(:ci_build, pipeline: pipeline, name: 'spinach', ref: 'feature', stage: 'test', stage_idx: 0) } + let!(:job) { create(:ci_build, :pending, :queued, pipeline: pipeline, name: 'spinach', ref: 'feature', stage: 'test', stage_idx: 0) } let(:merge_request) { create(:merge_request) } it 'sets branch as ref_type' do @@ -439,6 +439,13 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end end + it "sets the runner's config" do + request_job info: { 'config' => { 'gpus' => 'all', 'ignored' => 'hello' } } + + expect(response).to have_gitlab_http_status(:created) + expect(runner.reload.config).to eq( { 'gpus' => 'all' } ) + end + it "sets the runner's ip_address" do post api('/jobs/request'), params: { token: runner.token }, @@ -472,9 +479,9 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end context 'when project and pipeline have multiple jobs' do - let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } - let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } - let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) } + let!(:job) { create(:ci_build, :pending, :queued, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job2) { create(:ci_build, :pending, :queued, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } + let!(:test_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) } before do job.success @@ -524,8 +531,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end context 'when pipeline have jobs with artifacts' do - let!(:job) { create(:ci_build, :tag, :artifacts, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } - let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) } + let!(:job) { create(:ci_build, :pending, :queued, :tag, :artifacts, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:test_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) } before do job.success @@ -544,10 +551,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end context 'when explicit dependencies are defined' do - let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } - let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } + let!(:job) { create(:ci_build, :pending, :queued, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job2) { create(:ci_build, :pending, :queued, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } let!(:test_job) do - create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'deploy', + create(:ci_build, :pending, :queued, pipeline: pipeline, token: 'test-job-token', name: 'deploy', stage: 'deploy', stage_idx: 1, options: { script: ['bash'], dependencies: [job2.name] }) end @@ -568,10 +575,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end context 'when dependencies is an empty array' do - let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } - let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } + let!(:job) { create(:ci_build, :pending, :queued, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job2) { create(:ci_build, :pending, :queued, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } let!(:empty_dependencies_job) do - create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'empty_dependencies_job', + create(:ci_build, :pending, :queued, pipeline: pipeline, token: 'test-job-token', name: 'empty_dependencies_job', stage: 'deploy', stage_idx: 1, options: { script: ['bash'], dependencies: [] }) end @@ -732,7 +739,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end describe 'port support' do - let(:job) { create(:ci_build, pipeline: pipeline, options: options) } + let(:job) { create(:ci_build, :pending, :queued, pipeline: pipeline, options: options) } context 'when job image has ports' do let(:options) do @@ -784,7 +791,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do describe 'a job with excluded artifacts' do context 'when excluded paths are defined' do let(:job) do - create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'test', + create(:ci_build, :pending, :queued, pipeline: pipeline, token: 'test-job-token', name: 'test', stage: 'deploy', stage_idx: 1, options: { artifacts: { paths: ['abc'], exclude: ['cde'] } }) end @@ -832,7 +839,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do subject { request_job } context 'when triggered by a user' do - let(:job) { create(:ci_build, user: user, project: project) } + let(:job) { create(:ci_build, :pending, :queued, user: user, project: project) } subject { request_job(id: job.id) } diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb index b38630183f4..1696fe63d5d 100644 --- a/spec/requests/api/ci/runner/runners_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_post_spec.rb @@ -94,7 +94,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do context 'when it exceeds the application limits' do before do - create(:ci_runner, runner_type: :project_type, projects: [project]) + create(:ci_runner, runner_type: :project_type, projects: [project], contacted_at: 1.second.ago) create(:plan_limits, :default_plan, ci_registered_project_runners: 1) end @@ -106,6 +106,22 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do expect(project.runners.reload.size).to eq(1) end end + + context 'when abandoned runners cause application limits to not be exceeded' do + before do + create(:ci_runner, runner_type: :project_type, projects: [project], created_at: 14.months.ago, contacted_at: 13.months.ago) + create(:plan_limits, :default_plan, ci_registered_project_runners: 1) + end + + it 'creates runner' do + request + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['message']).to be_nil + expect(project.runners.reload.size).to eq(2) + expect(project.runners.recent.size).to eq(1) + end + end end context 'when group token is used' do @@ -135,7 +151,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do context 'when it exceeds the application limits' do before do - create(:ci_runner, runner_type: :group_type, groups: [group]) + create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 1.month.ago) create(:plan_limits, :default_plan, ci_registered_group_runners: 1) end @@ -147,6 +163,23 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do expect(group.runners.reload.size).to eq(1) end end + + context 'when abandoned runners cause application limits to not be exceeded' do + before do + create(:ci_runner, runner_type: :group_type, groups: [group], created_at: 4.months.ago, contacted_at: 3.months.ago) + create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 4.months.ago) + create(:plan_limits, :default_plan, ci_registered_group_runners: 1) + end + + it 'creates runner' do + request + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['message']).to be_nil + expect(group.runners.reload.size).to eq(3) + expect(group.runners.recent.size).to eq(1) + end + end end end diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb index 1727bc830fc..82fb4440429 100644 --- a/spec/requests/api/ci/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -137,11 +137,11 @@ RSpec.describe API::Ci::Runners do get api('/runners/all', admin) expect(json_response).to match_array [ - a_hash_including('description' => 'Project runner'), - a_hash_including('description' => 'Two projects runner'), - a_hash_including('description' => 'Group runner A'), - a_hash_including('description' => 'Group runner B'), - a_hash_including('description' => 'Shared runner') + a_hash_including('description' => 'Project runner', 'is_shared' => false, 'runner_type' => 'project_type'), + a_hash_including('description' => 'Two projects runner', 'is_shared' => false, 'runner_type' => 'project_type'), + a_hash_including('description' => 'Group runner A', 'is_shared' => false, 'runner_type' => 'group_type'), + a_hash_including('description' => 'Group runner B', 'is_shared' => false, 'runner_type' => 'group_type'), + a_hash_including('description' => 'Shared runner', 'is_shared' => true, 'runner_type' => 'instance_type') ] end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index ac125e81acd..ccc9f8c50c4 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -3,12 +3,12 @@ require 'spec_helper' RSpec.describe API::CommitStatuses do - let!(:project) { create(:project, :repository) } - let(:commit) { project.repository.commit } - let(:guest) { create_user(:guest) } - let(:reporter) { create_user(:reporter) } - let(:developer) { create_user(:developer) } - let(:sha) { commit.id } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:commit) { project.repository.commit } + let_it_be(:guest) { create_user(:guest) } + let_it_be(:reporter) { create_user(:reporter) } + let_it_be(:developer) { create_user(:developer) } + let_it_be(:sha) { commit.id } describe "GET /projects/:id/repository/commits/:sha/statuses" do let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" } @@ -233,27 +233,44 @@ RSpec.describe API::CommitStatuses do end end - context 'when updatig a commit status' do + context 'when updating a commit status' do + let(:parameters) do + { + state: 'success', + name: 'coverage', + ref: 'master' + } + end + + let(:updatable_optional_attributes) do + { + description: 'new description', + coverage: 90.0 + } + end + + # creating the initial commit status before do post api(post_url, developer), params: { state: 'running', context: 'coverage', ref: 'master', description: 'coverage test', - coverage: 0.0, + coverage: 10.0, target_url: 'http://gitlab.com/status' } + end + subject(:send_request) do post api(post_url, developer), params: { - state: 'success', - name: 'coverage', - ref: 'master', - description: 'new description', - coverage: 90.0 + **parameters, + **updatable_optional_attributes } end it 'updates a commit status' do + send_request + expect(response).to have_gitlab_http_status(:created) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') @@ -265,7 +282,28 @@ RSpec.describe API::CommitStatuses do end it 'does not create a new commit status' do - expect(CommitStatus.count).to eq 1 + expect { send_request }.not_to change { CommitStatus.count } + end + + context 'when the `state` parameter is sent the same' do + let(:parameters) do + { + state: 'running', + name: 'coverage', + ref: 'master' + } + end + + it 'does not update the commit status' do + send_request + + expect(response).to have_gitlab_http_status(:bad_request) + + commit_status = project.commit_statuses.find_by!(name: 'coverage') + + expect(commit_status.description).to eq('coverage test') + expect(commit_status.coverage).to eq(10.0) + end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index ac3aa808f37..1162ae76d15 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -284,6 +284,18 @@ RSpec.describe API::Commits do end end end + + context 'with the optional trailers parameter' do + it 'includes the Git trailers' do + get api("/projects/#{project_id}/repository/commits?ref_name=6d394385cf567f80a8fd85055db1ab4c5295806f&trailers=true", current_user) + + commit = json_response[0] + + expect(commit['trailers']).to eq( + 'Signed-off-by' => 'Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>' + ) + end + end end end @@ -1503,6 +1515,13 @@ RSpec.describe API::Commits do expect(json_response).to eq("dry_run" => "success") expect(project.commit(branch)).to eq(head) end + + it 'supports the use of a custom commit message' do + post api(route, user), params: { branch: branch, message: 'foo' } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response["message"]).to eq('foo') + end end context 'when repository is disabled' do diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb index 0ff88cb41a8..4120edabea3 100644 --- a/spec/requests/api/composer_packages_spec.rb +++ b/spec/requests/api/composer_packages_spec.rb @@ -9,6 +9,7 @@ RSpec.describe API::ComposerPackages do let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let_it_be(:package_name) { 'package-name' } let_it_be(:project, reload: true) { create(:project, :custom_repo, files: { 'composer.json' => { name: package_name }.to_json }, group: group) } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } let(:headers) { {} } using RSpec::Parameterized::TableSyntax @@ -428,6 +429,7 @@ RSpec.describe API::ComposerPackages 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) { { project: project, namespace: project.namespace } } before do project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) diff --git a/spec/requests/api/conan_instance_packages_spec.rb b/spec/requests/api/conan_instance_packages_spec.rb index 817530f0bad..ff3b332c620 100644 --- a/spec/requests/api/conan_instance_packages_spec.rb +++ b/spec/requests/api/conan_instance_packages_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe API::ConanInstancePackages do + let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } } + include_context 'conan api setup' describe 'GET /api/v4/packages/conan/v1/ping' do diff --git a/spec/requests/api/debian_group_packages_spec.rb b/spec/requests/api/debian_group_packages_spec.rb index 42c6c987872..c3abb06c5c1 100644 --- a/spec/requests/api/debian_group_packages_spec.rb +++ b/spec/requests/api/debian_group_packages_spec.rb @@ -7,33 +7,33 @@ RSpec.describe API::DebianGroupPackages do include_context 'Debian repository shared context', :group, false do describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release.gpg' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution}/Release.gpg" } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release.gpg" } it_behaves_like 'Debian repository read endpoint', 'GET request', :not_found end describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution}/Release" } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, 'TODO Release' + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO Release$/ end describe 'GET groups/:id/-/packages/debian/dists/*distribution/InRelease' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution}/InRelease" } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/InRelease" } it_behaves_like 'Debian repository read endpoint', 'GET request', :not_found end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do - let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution}/#{component}/binary-#{architecture}/Packages" } + let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component}/binary-#{architecture}/Packages" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, 'TODO Packages' + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO Packages$/ end describe 'GET groups/:id/-/packages/debian/pool/:component/:letter/:source_package/:file_name' do let(:url) { "/groups/#{container.id}/-/packages/debian/pool/#{component}/#{letter}/#{source_package}/#{package_name}_#{package_version}_#{architecture}.deb" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, 'TODO File' + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO File$/ end end end diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb index f400b6e928c..c11c4ecc12a 100644 --- a/spec/requests/api/debian_project_packages_spec.rb +++ b/spec/requests/api/debian_project_packages_spec.rb @@ -7,43 +7,55 @@ RSpec.describe API::DebianProjectPackages do include_context 'Debian repository shared context', :project, true do describe 'GET projects/:id/packages/debian/dists/*distribution/Release.gpg' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution}/Release.gpg" } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release.gpg" } it_behaves_like 'Debian repository read endpoint', 'GET request', :not_found end describe 'GET projects/:id/packages/debian/dists/*distribution/Release' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution}/Release" } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, 'TODO Release' + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO Release$/ end describe 'GET projects/:id/packages/debian/dists/*distribution/InRelease' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution}/InRelease" } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/InRelease" } it_behaves_like 'Debian repository read endpoint', 'GET request', :not_found end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do - let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution}/#{component}/binary-#{architecture}/Packages" } + let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component}/binary-#{architecture}/Packages" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, 'TODO Packages' + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO Packages$/ end describe 'GET projects/:id/packages/debian/pool/:component/:letter/:source_package/:file_name' do let(:url) { "/projects/#{container.id}/packages/debian/pool/#{component}/#{letter}/#{source_package}/#{package_name}_#{package_version}_#{architecture}.deb" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, 'TODO File' + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO File$/ end describe 'PUT projects/:id/packages/debian/:file_name' do let(:method) { :put } let(:url) { "/projects/#{container.id}/packages/debian/#{file_name}" } + let(:snowplow_gitlab_standard_context) { { project: container, user: user, namespace: container.namespace } } - it_behaves_like 'Debian repository write endpoint', 'upload request', :created + context 'with a deb' do + let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' } + + it_behaves_like 'Debian repository write endpoint', 'upload request', :created + end + + context 'with a changes file' do + let(:file_name) { 'sample_1.2.3~alpha2_amd64.changes' } + + it_behaves_like 'Debian repository write endpoint', 'upload request', :created + end end describe 'PUT projects/:id/packages/debian/:file_name/authorize' do + let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' } let(:method) { :put } let(:url) { "/projects/#{container.id}/packages/debian/#{file_name}/authorize" } diff --git a/spec/requests/api/feature_flag_scopes_spec.rb b/spec/requests/api/feature_flag_scopes_spec.rb deleted file mode 100644 index da5b2cbb7ae..00000000000 --- a/spec/requests/api/feature_flag_scopes_spec.rb +++ /dev/null @@ -1,319 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -RSpec.describe API::FeatureFlagScopes do - include FeatureFlagHelpers - - let(:project) { create(:project, :repository) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:user) { developer } - - before do - project.add_developer(developer) - project.add_reporter(reporter) - end - - shared_examples_for 'check user permission' do - context 'when user is reporter' do - let(:user) { reporter } - - it 'forbids the request' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - end - - shared_examples_for 'not found' do - it 'returns Not Found' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - describe 'GET /projects/:id/feature_flag_scopes' do - subject do - get api("/projects/#{project.id}/feature_flag_scopes", user), - params: params - end - - let(:feature_flag_1) { create_flag(project, 'flag_1', true) } - let(:feature_flag_2) { create_flag(project, 'flag_2', true) } - - before do - create_scope(feature_flag_1, 'staging', false) - create_scope(feature_flag_1, 'production', true) - create_scope(feature_flag_2, 'review/*', false) - end - - context 'when environment is production' do - let(:params) { { environment: 'production' } } - - it_behaves_like 'check user permission' - - it 'returns all effective feature flags under the environment' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('public_api/v4/feature_flag_detailed_scopes') - expect(json_response.second).to include({ 'name' => 'flag_1', 'active' => true }) - expect(json_response.first).to include({ 'name' => 'flag_2', 'active' => true }) - end - end - - context 'when environment is staging' do - let(:params) { { environment: 'staging' } } - - it 'returns all effective feature flags under the environment' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.second).to include({ 'name' => 'flag_1', 'active' => false }) - expect(json_response.first).to include({ 'name' => 'flag_2', 'active' => true }) - end - end - - context 'when environment is review/feature X' do - let(:params) { { environment: 'review/feature X' } } - - it 'returns all effective feature flags under the environment' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.second).to include({ 'name' => 'flag_1', 'active' => true }) - expect(json_response.first).to include({ 'name' => 'flag_2', 'active' => false }) - end - end - end - - describe 'GET /projects/:id/feature_flags/:name/scopes' do - subject do - get api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes", user) - end - - context 'when there are two scopes' do - let(:feature_flag) { create_flag(project, 'test') } - let!(:additional_scope) { create_scope(feature_flag, 'production', false) } - - it_behaves_like 'check user permission' - - it 'returns scopes of the feature flag' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('public_api/v4/feature_flag_scopes') - expect(json_response.count).to eq(2) - expect(json_response.first['environment_scope']).to eq(feature_flag.scopes[0].environment_scope) - expect(json_response.second['environment_scope']).to eq(feature_flag.scopes[1].environment_scope) - end - end - - context 'when there are no feature flags' do - let(:feature_flag) { double(:feature_flag, name: 'test') } - - it_behaves_like 'not found' - end - end - - describe 'POST /projects/:id/feature_flags/:name/scopes' do - subject do - post api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes", user), - params: params - end - - let(:params) do - { - environment_scope: 'staging', - active: true, - strategies: [{ name: 'userWithId', parameters: { 'userIds': 'a,b,c' } }].to_json - } - end - - context 'when there is a corresponding feature flag' do - let!(:feature_flag) { create(:operations_feature_flag, project: project) } - - it_behaves_like 'check user permission' - - it 'creates a new scope' do - subject - - expect(response).to have_gitlab_http_status(:created) - expect(response).to match_response_schema('public_api/v4/feature_flag_scope') - expect(json_response['environment_scope']).to eq(params[:environment_scope]) - expect(json_response['active']).to eq(params[:active]) - expect(json_response['strategies']).to eq(Gitlab::Json.parse(params[:strategies])) - end - - context 'when the scope already exists' do - before do - create_scope(feature_flag, params[:environment_scope]) - end - - it 'returns error' do - subject - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to include('Scopes environment scope (staging) has already been taken') - end - end - end - - context 'when feature flag is not found' do - let(:feature_flag) { double(:feature_flag, name: 'test') } - - it_behaves_like 'not found' - end - end - - describe 'GET /projects/:id/feature_flags/:name/scopes/:environment_scope' do - subject do - get api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes/#{environment_scope}", - user) - end - - let(:environment_scope) { scope.environment_scope } - - shared_examples_for 'successful response' do - it 'returns a scope' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('public_api/v4/feature_flag_scope') - expect(json_response['id']).to eq(scope.id) - expect(json_response['active']).to eq(scope.active) - expect(json_response['environment_scope']).to eq(scope.environment_scope) - end - end - - context 'when there is a feature flag' do - let!(:feature_flag) { create(:operations_feature_flag, project: project) } - let(:scope) { feature_flag.default_scope } - - it_behaves_like 'check user permission' - it_behaves_like 'successful response' - - context 'when environment scope includes slash' do - let!(:scope) { create_scope(feature_flag, 'review/*', false) } - - it_behaves_like 'not found' - - context 'when URL-encoding the environment scope parameter' do - let(:environment_scope) { CGI.escape(scope.environment_scope) } - - it_behaves_like 'successful response' - end - end - end - - context 'when there are no feature flags' do - let(:feature_flag) { double(:feature_flag, name: 'test') } - let(:scope) { double(:feature_flag_scope, environment_scope: 'prd') } - - it_behaves_like 'not found' - end - end - - describe 'PUT /projects/:id/feature_flags/:name/scopes/:environment_scope' do - subject do - put api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes/#{environment_scope}", - user), params: params - end - - let(:environment_scope) { scope.environment_scope } - - let(:params) do - { - active: true, - strategies: [{ name: 'userWithId', parameters: { 'userIds': 'a,b,c' } }].to_json - } - end - - context 'when there is a corresponding feature flag' do - let!(:feature_flag) { create(:operations_feature_flag, project: project) } - let(:scope) { create_scope(feature_flag, 'staging', false, [{ name: "default", parameters: {} }]) } - - it_behaves_like 'check user permission' - - it 'returns the updated scope' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('public_api/v4/feature_flag_scope') - expect(json_response['id']).to eq(scope.id) - expect(json_response['active']).to eq(params[:active]) - expect(json_response['strategies']).to eq(Gitlab::Json.parse(params[:strategies])) - end - - context 'when there are no corresponding feature flag scopes' do - let(:scope) { double(:feature_flag_scope, environment_scope: 'prd') } - - it_behaves_like 'not found' - end - end - - context 'when there are no corresponding feature flags' do - let(:feature_flag) { double(:feature_flag, name: 'test') } - let(:scope) { double(:feature_flag_scope, environment_scope: 'prd') } - - it_behaves_like 'not found' - end - end - - describe 'DELETE /projects/:id/feature_flags/:name/scopes/:environment_scope' do - subject do - delete api("/projects/#{project.id}/feature_flags/#{feature_flag.name}/scopes/#{environment_scope}", - user) - end - - let(:environment_scope) { scope.environment_scope } - - shared_examples_for 'successful response' do - it 'destroys the scope' do - expect { subject } - .to change { Operations::FeatureFlagScope.exists?(environment_scope: scope.environment_scope) } - .from(true).to(false) - - expect(response).to have_gitlab_http_status(:no_content) - end - end - - context 'when there is a feature flag' do - let!(:feature_flag) { create(:operations_feature_flag, project: project) } - - context 'when there is a targeted scope' do - let!(:scope) { create_scope(feature_flag, 'production', false) } - - it_behaves_like 'check user permission' - it_behaves_like 'successful response' - - context 'when environment scope includes slash' do - let!(:scope) { create_scope(feature_flag, 'review/*', false) } - - it_behaves_like 'not found' - - context 'when URL-encoding the environment scope parameter' do - let(:environment_scope) { CGI.escape(scope.environment_scope) } - - it_behaves_like 'successful response' - end - end - end - - context 'when there are no targeted scopes' do - let!(:scope) { double(:feature_flag_scope, environment_scope: 'production') } - - it_behaves_like 'not found' - end - end - - context 'when there are no feature flags' do - let(:feature_flag) { double(:feature_flag, name: 'test') } - let(:scope) { double(:feature_flag_scope, environment_scope: 'prd') } - - it_behaves_like 'not found' - end - end -end diff --git a/spec/requests/api/feature_flags_spec.rb b/spec/requests/api/feature_flags_spec.rb index dd12648f4dd..2cd52c0a5e5 100644 --- a/spec/requests/api/feature_flags_spec.rb +++ b/spec/requests/api/feature_flags_spec.rb @@ -62,7 +62,7 @@ RSpec.describe API::FeatureFlags do expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('public_api/v4/feature_flags') - expect(json_response.map { |f| f['version'] }).to eq(%w[legacy_flag legacy_flag]) + expect(json_response.map { |f| f['version'] }).to eq(%w[new_version_flag new_version_flag]) end it 'does not have N+1 problem' do @@ -145,7 +145,7 @@ RSpec.describe API::FeatureFlags do expect(response).to match_response_schema('public_api/v4/feature_flag') expect(json_response['name']).to eq(feature_flag.name) expect(json_response['description']).to eq(feature_flag.description) - expect(json_response['version']).to eq('legacy_flag') + expect(json_response['version']).to eq('new_version_flag') end it_behaves_like 'check user permission' @@ -453,210 +453,6 @@ RSpec.describe API::FeatureFlags do end end - describe 'POST /projects/:id/feature_flags/:name/enable' do - subject do - post api("/projects/#{project.id}/feature_flags/#{params[:name]}/enable", user), - params: params - end - - let(:params) do - { - name: 'awesome-feature', - environment_scope: 'production', - strategy: { name: 'userWithId', parameters: { userIds: 'Project:1' } }.to_json - } - end - - context 'when feature flag does not exist yet' do - it 'creates a new feature flag with the specified scope and strategy' do - subject - - feature_flag = project.operations_feature_flags.last - scope = feature_flag.scopes.find_by_environment_scope(params[:environment_scope]) - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('public_api/v4/feature_flag') - expect(feature_flag.name).to eq(params[:name]) - expect(scope.strategies).to eq([Gitlab::Json.parse(params[:strategy])]) - expect(feature_flag.version).to eq('legacy_flag') - end - - it 'returns the flag version and strategies in the json response' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('public_api/v4/feature_flag') - expect(json_response.slice('version', 'strategies')).to eq({ - 'version' => 'legacy_flag', - 'strategies' => [] - }) - end - - it_behaves_like 'check user permission' - end - - context 'when feature flag exists already' do - let!(:feature_flag) { create_flag(project, params[:name]) } - - context 'when feature flag scope does not exist yet' do - it 'creates a new scope with the specified strategy' do - subject - - scope = feature_flag.scopes.find_by_environment_scope(params[:environment_scope]) - expect(response).to have_gitlab_http_status(:ok) - expect(scope.strategies).to eq([Gitlab::Json.parse(params[:strategy])]) - end - - it_behaves_like 'check user permission' - end - - context 'when feature flag scope exists already' do - let(:defined_strategy) { { name: 'userWithId', parameters: { userIds: 'Project:2' } } } - - before do - create_scope(feature_flag, params[:environment_scope], true, [defined_strategy]) - end - - it 'adds an additional strategy to the scope' do - subject - - scope = feature_flag.scopes.find_by_environment_scope(params[:environment_scope]) - expect(response).to have_gitlab_http_status(:ok) - expect(scope.strategies).to eq([defined_strategy.deep_stringify_keys, Gitlab::Json.parse(params[:strategy])]) - end - - context 'when the specified strategy exists already' do - let(:defined_strategy) { Gitlab::Json.parse(params[:strategy]) } - - it 'does not add a duplicate strategy' do - subject - - scope = feature_flag.scopes.find_by_environment_scope(params[:environment_scope]) - strategy_count = scope.strategies.count { |strategy| strategy['name'] == 'userWithId' } - expect(response).to have_gitlab_http_status(:ok) - expect(strategy_count).to eq(1) - end - end - end - end - - context 'with a version 2 flag' do - let!(:feature_flag) { create(:operations_feature_flag, :new_version_flag, project: project, name: params[:name]) } - - it 'does not change the flag and returns an unprocessable_entity response' do - subject - - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response).to eq({ 'message' => 'Version 2 flags not supported' }) - feature_flag.reload - expect(feature_flag.scopes).to eq([]) - expect(feature_flag.strategies).to eq([]) - end - end - end - - describe 'POST /projects/:id/feature_flags/:name/disable' do - subject do - post api("/projects/#{project.id}/feature_flags/#{params[:name]}/disable", user), - params: params - end - - let(:params) do - { - name: 'awesome-feature', - environment_scope: 'production', - strategy: { name: 'userWithId', parameters: { userIds: 'Project:1' } }.to_json - } - end - - context 'when feature flag does not exist yet' do - it_behaves_like 'not found' - end - - context 'when feature flag exists already' do - let!(:feature_flag) { create_flag(project, params[:name]) } - - context 'when feature flag scope does not exist yet' do - it_behaves_like 'not found' - end - - context 'when feature flag scope exists already and has the specified strategy' do - let(:defined_strategies) do - [ - { name: 'userWithId', parameters: { userIds: 'Project:1' } }, - { name: 'userWithId', parameters: { userIds: 'Project:2' } } - ] - end - - before do - create_scope(feature_flag, params[:environment_scope], true, defined_strategies) - end - - it 'removes the strategy from the scope' do - subject - - scope = feature_flag.scopes.find_by_environment_scope(params[:environment_scope]) - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('public_api/v4/feature_flag') - expect(scope.strategies) - .to eq([{ name: 'userWithId', parameters: { userIds: 'Project:2' } }.deep_stringify_keys]) - end - - it 'returns the flag version and strategies in the json response' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('public_api/v4/feature_flag') - expect(json_response.slice('version', 'strategies')).to eq({ - 'version' => 'legacy_flag', - 'strategies' => [] - }) - end - - it_behaves_like 'check user permission' - - context 'when strategies become empty array after the removal' do - let(:defined_strategies) do - [{ name: 'userWithId', parameters: { userIds: 'Project:1' } }] - end - - it 'destroys the scope' do - subject - - scope = feature_flag.scopes.find_by_environment_scope(params[:environment_scope]) - expect(response).to have_gitlab_http_status(:ok) - expect(scope).to be_nil - end - - it_behaves_like 'check user permission' - end - end - - context 'when scope exists already but cannot find the corresponding strategy' do - let(:defined_strategy) { { name: 'userWithId', parameters: { userIds: 'Project:2' } } } - - before do - create_scope(feature_flag, params[:environment_scope], true, [defined_strategy]) - end - - it_behaves_like 'not found' - end - end - - context 'with a version 2 feature flag' do - let!(:feature_flag) { create(:operations_feature_flag, :new_version_flag, project: project, name: params[:name]) } - - it 'does not change the flag and returns an unprocessable_entity response' do - subject - - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response).to eq({ 'message' => 'Version 2 flags not supported' }) - feature_flag.reload - expect(feature_flag.scopes).to eq([]) - expect(feature_flag.strategies).to eq([]) - end - end - end - describe 'PUT /projects/:id/feature_flags/:name' do context 'with a legacy feature flag' do let!(:feature_flag) do @@ -664,13 +460,13 @@ RSpec.describe API::FeatureFlags do name: 'feature1', description: 'old description') end - it 'returns a 422' do + it 'returns a 404' do params = { description: 'new description' } put api("/projects/#{project.id}/feature_flags/feature1", user), params: params - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response).to eq({ 'message' => 'PUT operations are not supported for legacy feature flags' }) + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response).to eq({ 'message' => '404 Not Found' }) expect(feature_flag.reload.description).to eq('old description') end end @@ -984,7 +780,7 @@ RSpec.describe API::FeatureFlags do params: params end - let!(:feature_flag) { create(:operations_feature_flag, project: project) } + let!(:feature_flag) { create(:operations_feature_flag, :legacy_flag, project: project) } let(:params) { {} } it 'destroys the feature flag' do diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 71a4a1a2784..869df06b60c 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -558,8 +558,7 @@ RSpec.describe API::Files do get api(url, current_user), params: params - expect(response.headers["Cache-Control"]).to include("no-store") - expect(response.headers["Cache-Control"]).to include("no-cache") + 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/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb index a5e40eec919..378ee2f3e7c 100644 --- a/spec/requests/api/generic_packages_spec.rb +++ b/spec/requests/api/generic_packages_spec.rb @@ -18,7 +18,8 @@ RSpec.describe API::GenericPackages do let_it_be(:project_deploy_token_wo) { create(:project_deploy_token, deploy_token: deploy_token_wo, project: project) } let(:user) { personal_access_token.user } - let(:ci_build) { create(:ci_build, :running, user: user) } + let(:ci_build) { create(:ci_build, :running, user: user, project: project) } + let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } } def auth_header return {} if user_role == :anonymous diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb index e678b6cf1c8..0143340de11 100644 --- a/spec/requests/api/go_proxy_spec.rb +++ b/spec/requests/api/go_proxy_spec.rb @@ -11,7 +11,7 @@ RSpec.describe API::GoProxy do let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" } let_it_be(:oauth) { create :oauth_access_token, scopes: 'api', resource_owner: user } - let_it_be(:job) { create :ci_build, user: user, status: :running } + let_it_be(:job) { create :ci_build, user: user, status: :running, project: project } let_it_be(:pa_token) { create :personal_access_token, user: user } let_it_be(:modules) do diff --git a/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb index 99647d0fa3a..578a71a7272 100644 --- a/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb +++ b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb @@ -47,6 +47,7 @@ RSpec.describe 'Getting Ci Cd Setting' do expect(settings_data['mergePipelinesEnabled']).to eql project.ci_cd_settings.merge_pipelines_enabled? expect(settings_data['mergeTrainsEnabled']).to eql project.ci_cd_settings.merge_trains_enabled? expect(settings_data['keepLatestArtifact']).to eql project.keep_latest_artifacts_available? + expect(settings_data['jobTokenScopeEnabled']).to eql project.ci_cd_settings.job_token_scope_enabled? end end end diff --git a/spec/requests/api/graphql/group/group_members_spec.rb b/spec/requests/api/graphql/group/group_members_spec.rb index 452610ab18f..31cb0393d7f 100644 --- a/spec/requests/api/graphql/group/group_members_spec.rb +++ b/spec/requests/api/graphql/group/group_members_spec.rb @@ -14,6 +14,23 @@ RSpec.describe 'getting group members information' do [user_1, user_2].each { |user| parent_group.add_guest(user) } end + context 'when a member is invited only via email' do + before do + create(:group_member, :invited, source: parent_group) + end + + it 'returns null in the user field' do + fetch_members(group: parent_group, args: { relations: [:DIRECT] }) + + expect(graphql_errors).to be_nil + expect(graphql_data_at(:group, :group_members, :edges, :node)).to contain_exactly( + { 'user' => { 'id' => global_id_of(user_1) } }, + { 'user' => { 'id' => global_id_of(user_2) } }, + 'user' => nil + ) + end + end + context 'when the request is correct' do it_behaves_like 'a working graphql query' do before do diff --git a/spec/requests/api/graphql/group/milestones_spec.rb b/spec/requests/api/graphql/group/milestones_spec.rb index 601cab6aade..2b80b5239c8 100644 --- a/spec/requests/api/graphql/group/milestones_spec.rb +++ b/spec/requests/api/graphql/group/milestones_spec.rb @@ -40,6 +40,13 @@ RSpec.describe 'Milestones through GroupQuery' do expect_array_response(milestone_2.to_global_id.to_s, milestone_3.to_global_id.to_s) end + + it 'fetches milestones between timeframe start and end arguments' do + today = Date.today + fetch_milestones(user, { timeframe: { start: today.to_s, end: (today + 2.days).to_s } }) + + expect_array_response(milestone_2.to_global_id.to_s, milestone_3.to_global_id.to_s) + end end context 'when filtering by state' do diff --git a/spec/requests/api/graphql/group/timelogs_spec.rb b/spec/requests/api/graphql/group/timelogs_spec.rb index 6e21a73afa9..05b6ee3ff89 100644 --- a/spec/requests/api/graphql/group/timelogs_spec.rb +++ b/spec/requests/api/graphql/group/timelogs_spec.rb @@ -17,8 +17,37 @@ RSpec.describe 'Timelogs through GroupQuery' do let(:timelogs_data) { graphql_data['group']['timelogs']['nodes'] } - before do - group.add_developer(user) + context 'when the project is private' do + let_it_be(:group2) { create(:group) } + let_it_be(:project2) { create(:project, :private, group: group2) } + let_it_be(:issue2) { create(:issue, project: project2) } + let_it_be(:timelog3) { create(:timelog, issue: issue2, spent_at: '2019-08-13 14:00:00') } + + subject { post_graphql(query(full_path: group2.full_path), current_user: user) } + + context 'when the user is not a member of the project' do + it 'returns no timelogs' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(graphql_errors).to be_nil + expect(timelog_array.size).to eq 0 + end + end + + context 'when the user is a member of the project' do + before do + project2.add_developer(user) + end + + it 'returns timelogs' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(graphql_errors).to be_nil + expect(timelog_array.size).to eq 1 + end + end end context 'when the request is correct' do @@ -74,18 +103,6 @@ RSpec.describe 'Timelogs through GroupQuery' do expect(timelogs_data).to be_empty end end - - context 'when user has no permission to read group timelogs' do - it 'returns empty result' do - guest = create(:user) - group.add_guest(guest) - post_graphql(query, current_user: guest) - - expect(response).to have_gitlab_http_status(:success) - expect(graphql_errors).to be_nil - expect(timelogs_data).to be_empty - end - end end end @@ -95,7 +112,7 @@ RSpec.describe 'Timelogs through GroupQuery' do end end - def query(timelog_params = params) + def query(timelog_params: params, full_path: group.full_path) timelog_nodes = <<~NODE nodes { spentAt @@ -114,7 +131,7 @@ RSpec.describe 'Timelogs through GroupQuery' do graphql_query_for( :group, - { full_path: group.full_path }, + { full_path: full_path }, query_graphql_field(:timelogs, timelog_params, timelog_nodes) ) end diff --git a/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb b/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb index 0dcae28ac5d..0d7571d91ca 100644 --- a/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/ci_cd_settings_update_spec.rb @@ -5,8 +5,16 @@ require 'spec_helper' RSpec.describe 'CiCdSettingsUpdate' do include GraphqlHelpers - let_it_be(:project) { create(:project, keep_latest_artifact: true) } - let(:variables) { { full_path: project.full_path, keep_latest_artifact: false } } + let_it_be(:project) { create(:project, keep_latest_artifact: true, ci_job_token_scope_enabled: true) } + + let(:variables) do + { + full_path: project.full_path, + keep_latest_artifact: false, + job_token_scope_enabled: false + } + end + let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) } context 'when unauthorized' do @@ -45,6 +53,26 @@ RSpec.describe 'CiCdSettingsUpdate' do expect(project.keep_latest_artifact).to eq(false) end + it 'updates 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_job_token_scope_enabled).to eq(false) + end + + it 'does not update job_token_scope_enabled if not specified' do + variables.except!(:job_token_scope_enabled) + + post_graphql_mutation(mutation, current_user: user) + + project.reload + + expect(response).to have_gitlab_http_status(:success) + expect(project.ci_job_token_scope_enabled).to eq(true) + end + context 'when bad arguments are provided' do let(:variables) { { full_path: '', keep_latest_artifact: false } } diff --git a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb new file mode 100644 index 00000000000..07b05ead651 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'RunnersRegistrationTokenReset' do + include GraphqlHelpers + + let(:mutation) { graphql_mutation(:runners_registration_token_reset, input) } + let(:mutation_response) { graphql_mutation_response(:runners_registration_token_reset) } + + subject { post_graphql_mutation(mutation, current_user: user) } + + shared_examples 'unauthorized' do + it 'returns an error' do + subject + + expect(graphql_errors).not_to be_empty + expect(graphql_errors).to include(a_hash_including('message' => "The resource that you are attempting to access does not exist or you don't have permission to perform this action")) + expect(mutation_response).to be_nil + end + end + + shared_context 'when unauthorized' do |scope| + context 'when unauthorized' do + let_it_be(:user) { create(:user) } + + context "when not a #{scope} member" do + it_behaves_like 'unauthorized' + end + + context "with a non-admin #{scope} member" do + before do + target.add_developer(user) + end + + it_behaves_like 'unauthorized' + end + end + end + + shared_context 'when authorized' do |scope| + it 'resets runner registration token' do + expect { subject }.to change { get_token } + expect(response).to have_gitlab_http_status(:success) + + expect(mutation_response).not_to be_nil + expect(mutation_response['errors']).to be_empty + expect(mutation_response['token']).not_to be_empty + expect(mutation_response['token']).to eq(get_token) + end + + context 'when malformed id is provided' do + let(:input) { { type: "#{scope.upcase}_TYPE", id: 'some string' } } + + it 'returns errors' do + expect { subject }.not_to change { get_token } + + expect(graphql_errors).not_to be_empty + expect(mutation_response).to be_nil + end + end + end + + context 'applied to project' do + let_it_be(:project) { create_default(:project) } + + let(:input) { { type: 'PROJECT_TYPE', id: project.to_global_id.to_s } } + + include_context 'when unauthorized', 'project' do + let(:target) { project } + end + + include_context 'when authorized', 'project' do + let_it_be(:user) { project.owner } + + def get_token + project.reload.runners_token + end + end + end + + context 'applied to group' do + let_it_be(:group) { create_default(:group) } + + let(:input) { { type: 'GROUP_TYPE', id: group.to_global_id.to_s } } + + include_context 'when unauthorized', 'group' do + let(:target) { group } + end + + include_context 'when authorized', 'group' do + let_it_be(:user) { create_default(:group_member, :maintainer, user: create(:user), group: group ).user } + + def get_token + group.reload.runners_token + end + end + end + + context 'applied to instance' do + before do + ApplicationSetting.create_from_defaults + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + + let(:input) { { type: 'INSTANCE_TYPE' } } + + context 'when unauthorized' do + let(:user) { create(:user) } + + it_behaves_like 'unauthorized' + end + + include_context 'when authorized', 'instance' do + let_it_be(:user) { create(:user, :admin) } + + def get_token + ApplicationSetting.current_without_cache.runners_registration_token + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/labels/create_spec.rb b/spec/requests/api/graphql/mutations/labels/create_spec.rb index ca3ccc8e06c..28284408306 100644 --- a/spec/requests/api/graphql/mutations/labels/create_spec.rb +++ b/spec/requests/api/graphql/mutations/labels/create_spec.rb @@ -11,8 +11,7 @@ RSpec.describe Mutations::Labels::Create do { 'title' => 'foo', 'description' => 'some description', - 'color' => '#FF0000', - 'removeOnClose' => true + 'color' => '#FF0000' } end diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb index d944c9e9e57..214c804c519 100644 --- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb @@ -86,7 +86,7 @@ RSpec.describe 'Creating a Snippet' do it 'passes disable_spam_action_service param to service' do expect(::Snippets::CreateService) .to receive(:new) - .with(anything, anything, hash_including(disable_spam_action_service: true)) + .with(project: anything, current_user: anything, params: hash_including(disable_spam_action_service: true)) .and_call_original subject @@ -190,7 +190,7 @@ RSpec.describe 'Creating a Snippet' do it do expect(::Snippets::CreateService).to receive(:new) - .with(nil, user, hash_including(files: expected_value)) + .with(project: nil, current_user: user, params: hash_including(files: expected_value)) .and_return(double(execute: creation_response)) subject diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb index 28ab593526a..77efb786dcb 100644 --- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb @@ -90,7 +90,7 @@ RSpec.describe 'Updating a Snippet' do it 'passes disable_spam_action_service param to service' do expect(::Snippets::UpdateService) .to receive(:new) - .with(anything, anything, hash_including(disable_spam_action_service: true)) + .with(project: anything, current_user: anything, params: hash_including(disable_spam_action_service: true)) .and_call_original subject diff --git a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb index 705ef28ffd4..8f92105dc9c 100644 --- a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb +++ b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb @@ -22,8 +22,8 @@ RSpec.describe 'Marking all todos done' do graphql_mutation(:todos_mark_all_done, input, <<-QL.strip_heredoc clientMutationId + todos { id } errors - updatedIds QL ) end @@ -40,7 +40,7 @@ RSpec.describe 'Marking all todos done' do expect(todo3.reload.state).to eq('done') expect(other_user_todo.reload.state).to eq('pending') - updated_todo_ids = mutation_response['updatedIds'] + updated_todo_ids = mutation_response['todos'].map { |todo| todo['id'] } expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3)) end @@ -52,7 +52,7 @@ RSpec.describe 'Marking all todos done' do expect(todo3.reload.state).to eq('pending') expect(other_user_todo.reload.state).to eq('pending') - updated_todo_ids = mutation_response['updatedIds'] + updated_todo_ids = mutation_response['todos'] expect(updated_todo_ids).to be_empty end diff --git a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb index 3e96d5c5058..e71a232ff7c 100644 --- a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb +++ b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb @@ -22,7 +22,6 @@ RSpec.describe 'Restoring many Todos' do <<-QL.strip_heredoc clientMutationId errors - updatedIds todos { id state @@ -44,7 +43,6 @@ RSpec.describe 'Restoring many Todos' do expect(mutation_response).to include( 'errors' => be_empty, - 'updatedIds' => match_array(input_ids), 'todos' => contain_exactly( { 'id' => global_id_of(todo1), 'state' => 'pending' }, { 'id' => global_id_of(todo2), 'state' => 'pending' } diff --git a/spec/requests/api/graphql/packages/composer_spec.rb b/spec/requests/api/graphql/packages/composer_spec.rb index 34137a07c34..9830623ede8 100644 --- a/spec/requests/api/graphql/packages/composer_spec.rb +++ b/spec/requests/api/graphql/packages/composer_spec.rb @@ -3,62 +3,35 @@ require 'spec_helper' RSpec.describe 'package details' do include GraphqlHelpers + include_context 'package details setup' - let_it_be(:project) { create(:project) } - let_it_be(:composer_package) { create(:composer_package, project: project) } + let_it_be(:package) { create(:composer_package, project: project) } let_it_be(:composer_json) { { name: 'name', type: 'type', license: 'license', version: 1 } } let_it_be(:composer_metadatum) do # we are forced to manually create the metadatum, without using the factory to force the sha to be a string # and avoid an error where gitaly can't find the repository - create(:composer_metadatum, package: composer_package, target_sha: 'foo_sha', composer_json: composer_json) + create(:composer_metadatum, package: package, target_sha: 'foo_sha', composer_json: composer_json) end - let(:depth) { 3 } - let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] } let(:metadata) { query_graphql_fragment('ComposerMetadata') } - let(:package_files) { all_graphql_fields_for('PackageFile') } - let(:user) { project.owner } - let(:package_global_id) { global_id_of(composer_package) } - let(:package_details) { graphql_data_at(:package) } - let(:metadata_response) { graphql_data_at(:package, :metadata) } let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) } - let(:query) do - graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) - #{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)} - metadata { - #{metadata} - } - packageFiles { - nodes { - #{package_files} - } - } - FIELDS - end - subject { post_graphql(query, current_user: user) } before do subject end - it_behaves_like 'a working graphql query' do - it 'matches the JSON schema' do - expect(package_details).to match_schema('graphql/packages/package_details') - end - end + it_behaves_like 'a package detail' - describe 'Composer' do - it 'has the correct metadata' do - expect(metadata_response).to include( - 'targetSha' => 'foo_sha', - 'composerJson' => composer_json.transform_keys(&:to_s).transform_values(&:to_s) - ) - end + it 'has the correct metadata' do + expect(metadata_response).to include( + 'targetSha' => 'foo_sha', + 'composerJson' => composer_json.transform_keys(&:to_s).transform_values(&:to_s) + ) + end - it 'does not have files' do - expect(package_files_response).to be_empty - end + it 'does not have files' do + expect(package_files_response).to be_empty end end diff --git a/spec/requests/api/graphql/packages/conan_spec.rb b/spec/requests/api/graphql/packages/conan_spec.rb index dc64c5057d5..84c5af33e5d 100644 --- a/spec/requests/api/graphql/packages/conan_spec.rb +++ b/spec/requests/api/graphql/packages/conan_spec.rb @@ -3,26 +3,13 @@ require 'spec_helper' RSpec.describe 'conan package details' do include GraphqlHelpers + include_context 'package details setup' - let_it_be(:project) { create(:project) } - let_it_be(:conan_package) { create(:conan_package, project: project) } + let_it_be(:package) { create(:conan_package, project: project) } - let(:package_global_id) { global_id_of(conan_package) } let(:metadata) { query_graphql_fragment('ConanMetadata') } - let(:first_file) { conan_package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } } - - let(:depth) { 3 } - let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] } - let(:package_files) { all_graphql_fields_for('PackageFile') } let(:package_files_metadata) {query_graphql_fragment('ConanFileMetadata')} - let(:user) { project.owner } - let(:package_details) { graphql_data_at(:package) } - let(:metadata_response) { graphql_data_at(:package, :metadata) } - let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) } - let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)} - let(:first_file_response_metadata) { graphql_data_at(:package, :package_files, :nodes, 0, :file_metadata)} - let(:query) do graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) #{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)} @@ -46,35 +33,16 @@ RSpec.describe 'conan package details' do subject end - it_behaves_like 'a working graphql query' do - it 'matches the JSON schema' do - expect(package_details).to match_schema('graphql/packages/package_details') - end - end + it_behaves_like 'a package detail' + it_behaves_like 'a package with files' it 'has the correct metadata' do expect(metadata_response).to include( - 'id' => global_id_of(conan_package.conan_metadatum), - 'recipe' => conan_package.conan_metadatum.recipe, - 'packageChannel' => conan_package.conan_metadatum.package_channel, - 'packageUsername' => conan_package.conan_metadatum.package_username, - 'recipePath' => conan_package.conan_metadatum.recipe_path - ) - end - - it 'has the right amount of files' do - expect(package_files_response.length).to be(conan_package.package_files.length) - end - - it 'has the basic package files data' do - expect(first_file_response).to include( - 'id' => global_id_of(first_file), - 'fileName' => first_file.file_name, - 'size' => first_file.size.to_s, - 'downloadPath' => first_file.download_path, - 'fileSha1' => first_file.file_sha1, - 'fileMd5' => first_file.file_md5, - 'fileSha256' => first_file.file_sha256 + 'id' => global_id_of(package.conan_metadatum), + 'recipe' => package.conan_metadatum.recipe, + 'packageChannel' => package.conan_metadatum.package_channel, + 'packageUsername' => package.conan_metadatum.package_username, + 'recipePath' => package.conan_metadatum.recipe_path ) end diff --git a/spec/requests/api/graphql/packages/maven_spec.rb b/spec/requests/api/graphql/packages/maven_spec.rb index 8b6b5ea0986..d28d32b0df5 100644 --- a/spec/requests/api/graphql/packages/maven_spec.rb +++ b/spec/requests/api/graphql/packages/maven_spec.rb @@ -3,89 +3,51 @@ require 'spec_helper' RSpec.describe 'maven package details' do include GraphqlHelpers + include_context 'package details setup' - let_it_be(:project) { create(:project) } - let_it_be(:maven_package) { create(:maven_package, project: project) } + let_it_be(:package) { create(:maven_package, project: project) } - let(:package_global_id) { global_id_of(maven_package) } let(:metadata) { query_graphql_fragment('MavenMetadata') } - let(:first_file) { maven_package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } } - - let(:depth) { 3 } - let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] } - let(:package_files) { all_graphql_fields_for('PackageFile') } - - let(:user) { project.owner } - let(:package_details) { graphql_data_at(:package) } - let(:metadata_response) { graphql_data_at(:package, :metadata) } - let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) } - let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)} - - let(:query) do - graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) - #{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)} - metadata { - #{metadata} - } - packageFiles { - nodes { - #{package_files} - } - } - FIELDS - end - - subject { post_graphql(query, current_user: user) } - - shared_examples 'a working maven package' do - before do - subject - end - - it_behaves_like 'a working graphql query' do - it 'matches the JSON schema' do - expect(package_details).to match_schema('graphql/packages/package_details') - end - end + shared_examples 'correct maven metadata' do it 'has the correct metadata' do expect(metadata_response).to include( - 'id' => global_id_of(maven_package.maven_metadatum), - 'path' => maven_package.maven_metadatum.path, - 'appGroup' => maven_package.maven_metadatum.app_group, - 'appVersion' => maven_package.maven_metadatum.app_version, - 'appName' => maven_package.maven_metadatum.app_name + 'id' => global_id_of(package.maven_metadatum), + 'path' => package.maven_metadatum.path, + 'appGroup' => package.maven_metadatum.app_group, + 'appVersion' => package.maven_metadatum.app_version, + 'appName' => package.maven_metadatum.app_name ) end + end - it 'has the right amount of files' do - expect(package_files_response.length).to be(maven_package.package_files.length) - end + context 'a maven package with version' do + subject { post_graphql(query, current_user: user) } - it 'has the basic package files data' do - expect(first_file_response).to include( - 'id' => global_id_of(first_file), - 'fileName' => first_file.file_name, - 'size' => first_file.size.to_s, - 'downloadPath' => first_file.download_path, - 'fileSha1' => first_file.file_sha1, - 'fileMd5' => first_file.file_md5, - 'fileSha256' => first_file.file_sha256 - ) + before do + subject end - end - context 'a maven package with version' do - it_behaves_like "a working maven package" + it_behaves_like 'a package detail' + it_behaves_like 'correct maven metadata' + it_behaves_like 'a package with files' end context 'a versionless maven package' do let_it_be(:maven_metadatum) { create(:maven_metadatum, app_version: nil) } - let_it_be(:maven_package) { create(:maven_package, project: project, version: nil, maven_metadatum: maven_metadatum) } + let_it_be(:package) { create(:maven_package, project: project, version: nil, maven_metadatum: maven_metadatum) } + + subject { post_graphql(query, current_user: user) } + + before do + subject + end - it_behaves_like "a working maven package" + it_behaves_like 'a package detail' + it_behaves_like 'correct maven metadata' + it_behaves_like 'a package with files' - it "has an empty version" do + it 'has an empty version' do subject expect(metadata_response['appVersion']).to eq(nil) diff --git a/spec/requests/api/graphql/packages/nuget_spec.rb b/spec/requests/api/graphql/packages/nuget_spec.rb index fa9d8a0e37e..1de16009684 100644 --- a/spec/requests/api/graphql/packages/nuget_spec.rb +++ b/spec/requests/api/graphql/packages/nuget_spec.rb @@ -3,37 +3,11 @@ require 'spec_helper' RSpec.describe 'nuget package details' do include GraphqlHelpers + include_context 'package details setup' - let_it_be(:project) { create(:project) } - let_it_be(:nuget_package) { create(:nuget_package, :with_metadatum, project: project) } + let_it_be(:package) { create(:nuget_package, :with_metadatum, project: project) } - let(:package_global_id) { global_id_of(nuget_package) } let(:metadata) { query_graphql_fragment('NugetMetadata') } - let(:first_file) { nuget_package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } } - - let(:depth) { 3 } - let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] } - let(:package_files) { all_graphql_fields_for('PackageFile') } - - let(:user) { project.owner } - let(:package_details) { graphql_data_at(:package) } - let(:metadata_response) { graphql_data_at(:package, :metadata) } - let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) } - let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)} - - let(:query) do - graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) - #{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)} - metadata { - #{metadata} - } - packageFiles { - nodes { - #{package_files} - } - } - FIELDS - end subject { post_graphql(query, current_user: user) } @@ -41,34 +15,15 @@ RSpec.describe 'nuget package details' do subject end - it_behaves_like 'a working graphql query' do - it 'matches the JSON schema' do - expect(package_details).to match_schema('graphql/packages/package_details') - end - end + it_behaves_like 'a package detail' + it_behaves_like 'a package with files' it 'has the correct metadata' do expect(metadata_response).to include( - 'id' => global_id_of(nuget_package.nuget_metadatum), - 'licenseUrl' => nuget_package.nuget_metadatum.license_url, - 'projectUrl' => nuget_package.nuget_metadatum.project_url, - 'iconUrl' => nuget_package.nuget_metadatum.icon_url - ) - end - - it 'has the right amount of files' do - expect(package_files_response.length).to be(nuget_package.package_files.length) - end - - it 'has the basic package files data' do - expect(first_file_response).to include( - 'id' => global_id_of(first_file), - 'fileName' => first_file.file_name, - 'size' => first_file.size.to_s, - 'downloadPath' => first_file.download_path, - 'fileSha1' => first_file.file_sha1, - 'fileMd5' => first_file.file_md5, - 'fileSha256' => first_file.file_sha256 + 'id' => global_id_of(package.nuget_metadatum), + 'licenseUrl' => package.nuget_metadatum.license_url, + 'projectUrl' => package.nuget_metadatum.project_url, + 'iconUrl' => package.nuget_metadatum.icon_url ) end end diff --git a/spec/requests/api/graphql/packages/pypi_spec.rb b/spec/requests/api/graphql/packages/pypi_spec.rb new file mode 100644 index 00000000000..64fe7d29a7a --- /dev/null +++ b/spec/requests/api/graphql/packages/pypi_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'pypi package details' do + include GraphqlHelpers + include_context 'package details setup' + + let_it_be(:package) { create(:pypi_package, project: project) } + + let(:metadata) { query_graphql_fragment('PypiMetadata') } + + subject { post_graphql(query, current_user: user) } + + before do + subject + end + + it_behaves_like 'a package detail' + it_behaves_like 'a package with files' + + it 'has the correct metadata' do + expect(metadata_response).to include( + 'id' => global_id_of(package.pypi_metadatum), + 'requiredPython' => package.pypi_metadatum.required_python + ) + end +end diff --git a/spec/requests/api/graphql/project/base_service_spec.rb b/spec/requests/api/graphql/project/base_service_spec.rb index 4dfc242da80..af462c4a639 100644 --- a/spec/requests/api/graphql/project/base_service_spec.rb +++ b/spec/requests/api/graphql/project/base_service_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'query Jira service' do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:jira_service) { create(:jira_service, project: project) } - let_it_be(:bugzilla_service) { create(:bugzilla_service, project: project) } + let_it_be(:bugzilla_integration) { create(:bugzilla_integration, project: project) } let_it_be(:redmine_service) { create(:redmine_service, project: project) } let(:query) do diff --git a/spec/requests/api/graphql/project/project_members_spec.rb b/spec/requests/api/graphql/project/project_members_spec.rb index c08bb8dc0a0..466464f600c 100644 --- a/spec/requests/api/graphql/project/project_members_spec.rb +++ b/spec/requests/api/graphql/project/project_members_spec.rb @@ -50,6 +50,20 @@ RSpec.describe 'getting project members information' do invited_group.add_guest(invited_user) end + context 'when a member is invited only via email and current_user is a maintainer' do + before do + parent_project.add_maintainer(user) + create(:project_member, :invited, source: parent_project) + end + + it 'returns null in the user field' do + fetch_members(project: parent_project, args: { relations: [:DIRECT] }) + + expect(graphql_errors).to be_nil + expect(graphql_data_at(:project, :project_members, :edges, :node)).to contain_exactly({ 'user' => { 'id' => global_id_of(user) } }, 'user' => nil) + end + end + it 'returns direct members' do fetch_members(project: child_project, args: { relations: [:DIRECT] }) diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb index 43732c2ed18..8ccdb955ed9 100644 --- a/spec/requests/api/graphql/project/releases_spec.rb +++ b/spec/requests/api/graphql/project/releases_spec.rb @@ -295,75 +295,69 @@ RSpec.describe 'Query.project(fullPath).releases()' do end end - describe 'sorting behavior' do - let_it_be(:today) { Time.now } - let_it_be(:yesterday) { today - 1.day } - let_it_be(:tomorrow) { today + 1.day } + describe 'sorting and pagination' do + let_it_be(:sort_project) { create(:project, :public) } - let_it_be(:project) { create(:project, :repository, :public) } + let(:data_path) { [:project, :releases] } + let(:current_user) { developer } - let_it_be(:release_v1) { create(:release, project: project, tag: 'v1', released_at: yesterday, created_at: tomorrow) } - let_it_be(:release_v2) { create(:release, project: project, tag: 'v2', released_at: today, created_at: yesterday) } - let_it_be(:release_v3) { create(:release, project: project, tag: 'v3', released_at: tomorrow, created_at: today) } - - let(:current_user) { developer } - - let(:params) { nil } - - let(:sorted_tags) do - graphql_data.dig('project', 'releases', 'nodes').map { |release| release['tagName'] } - end - - let(:query) do - graphql_query_for(:project, { fullPath: project.full_path }, - %{ - releases#{params ? "(#{params})" : ""} { - nodes { - tagName - } - } - }) - end - - before do - post_query + def pagination_query(params) + graphql_query_for( + :project, + { full_path: sort_project.full_path }, + query_graphql_field(:releases, params, "#{page_info} nodes { tagName }") + ) end - context 'when no sort: parameter is provided' do - it 'returns the results with the default sort applied (sort: RELEASED_AT_DESC)' do - expect(sorted_tags).to eq(%w(v3 v2 v1)) - end + def pagination_results_data(nodes) + nodes.map { |release| release['tagName'] } end - context 'with sort: RELEASED_AT_DESC' do - let(:params) { 'sort: RELEASED_AT_DESC' } - - it 'returns the releases ordered by released_at in descending order' do - expect(sorted_tags).to eq(%w(v3 v2 v1)) + context 'when sorting by released_at' do + let_it_be(:release5) { create(:release, project: sort_project, tag: 'v5.5.0', released_at: 3.days.from_now) } + let_it_be(:release1) { create(:release, project: sort_project, tag: 'v5.1.0', released_at: 3.days.ago) } + let_it_be(:release4) { create(:release, project: sort_project, tag: 'v5.4.0', released_at: 2.days.from_now) } + let_it_be(:release2) { create(:release, project: sort_project, tag: 'v5.2.0', released_at: 2.days.ago) } + let_it_be(:release3) { create(:release, project: sort_project, tag: 'v5.3.0', released_at: 1.day.ago) } + + context 'when ascending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :RELEASED_AT_ASC } + let(:first_param) { 2 } + let(:expected_results) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] } + end end - end - context 'with sort: RELEASED_AT_ASC' do - let(:params) { 'sort: RELEASED_AT_ASC' } - - it 'returns the releases ordered by released_at in ascending order' do - expect(sorted_tags).to eq(%w(v1 v2 v3)) + context 'when descending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :RELEASED_AT_DESC } + let(:first_param) { 2 } + let(:expected_results) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] } + end end end - context 'with sort: CREATED_DESC' do - let(:params) { 'sort: CREATED_DESC' } - - it 'returns the releases ordered by created_at in descending order' do - expect(sorted_tags).to eq(%w(v1 v3 v2)) + context 'when sorting by created_at' do + let_it_be(:release5) { create(:release, project: sort_project, tag: 'v5.5.0', created_at: 3.days.from_now) } + let_it_be(:release1) { create(:release, project: sort_project, tag: 'v5.1.0', created_at: 3.days.ago) } + let_it_be(:release4) { create(:release, project: sort_project, tag: 'v5.4.0', created_at: 2.days.from_now) } + let_it_be(:release2) { create(:release, project: sort_project, tag: 'v5.2.0', created_at: 2.days.ago) } + let_it_be(:release3) { create(:release, project: sort_project, tag: 'v5.3.0', created_at: 1.day.ago) } + + context 'when ascending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :CREATED_ASC } + let(:first_param) { 2 } + let(:expected_results) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] } + end end - end - - context 'with sort: CREATED_ASC' do - let(:params) { 'sort: CREATED_ASC' } - it 'returns the releases ordered by created_at in ascending order' do - expect(sorted_tags).to eq(%w(v2 v3 v1)) + context 'when descending' do + it_behaves_like 'sorted paginated query' do + let(:sort_param) { :CREATED_DESC } + let(:first_param) { 2 } + let(:expected_results) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] } + end end end end diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb index b367bbaaf43..54375d4de1d 100644 --- a/spec/requests/api/graphql/project_query_spec.rb +++ b/spec/requests/api/graphql/project_query_spec.rb @@ -65,7 +65,7 @@ RSpec.describe 'getting project information' do end it 'includes topics array' do - project.update!(tag_list: 'topic1, topic2, topic3') + project.update!(topic_list: 'topic1, topic2, topic3') post_graphql(query, current_user: current_user) @@ -119,6 +119,29 @@ RSpec.describe 'getting project information' do end end + context 'when the user has reporter access to the project' do + let(:statistics_query) do + <<~GRAPHQL + { + project(fullPath: "#{project.full_path}") { + statistics { wikiSize } + } + } + GRAPHQL + end + + before do + project.add_reporter(current_user) + create(:project_statistics, project: project, wiki_size: 100) + end + + it 'allows fetching project statistics' do + post_graphql(statistics_query, current_user: current_user) + + expect(graphql_data.dig('project', 'statistics')).to include('wikiSize' => 100.0) + end + end + context 'when the user does not have access to the project' do it 'returns an empty field' do post_graphql(query, current_user: current_user) diff --git a/spec/requests/api/group_avatar_spec.rb b/spec/requests/api/group_avatar_spec.rb new file mode 100644 index 00000000000..be5cfbc234c --- /dev/null +++ b/spec/requests/api/group_avatar_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::GroupAvatar do + def avatar_path(group) + "/groups/#{group.id}/avatar" + end + + describe 'GET /groups/:id/avatar' do + context 'when the group is public' do + it 'retrieves the avatar successfully' do + group = create(:group, :public, :with_avatar) + + get api(avatar_path(group)) + + expect(response).to have_gitlab_http_status(:ok) + end + + context 'when the group does not have avatar' do + it 'returns :not_found' do + group = create(:group, :public) + + get api(avatar_path(group)) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + context 'when the group is private' do + let(:group) { create(:group, :private, :with_avatar) } + + context 'when the user is not authenticated' do + it 'returns :not_found' do + get api(avatar_path(group)) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when the the group user is authenticated' do + context 'and have access to the group' do + it 'retrieves the avatar successfully' do + owner = create(:user) + group.add_owner(owner) + + get api(avatar_path(group), owner) + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'and does not have access to the group' do + it 'returns :not_found' do + get api(avatar_path(group), create(:user)) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end + end +end diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb index 4584ef37bd0..fdbf910e4bc 100644 --- a/spec/requests/api/group_container_repositories_spec.rb +++ b/spec/requests/api/group_container_repositories_spec.rb @@ -33,6 +33,7 @@ RSpec.describe API::GroupContainerRepositories do describe 'GET /groups/:id/registry/repositories' do let(:url) { "/groups/#{group.id}/registry/repositories" } + let(:snowplow_gitlab_standard_context) { { user: api_user, namespace: group } } subject { get api(url, api_user) } diff --git a/spec/requests/api/group_export_spec.rb b/spec/requests/api/group_export_spec.rb index 8309e2ba7c1..31eef21654a 100644 --- a/spec/requests/api/group_export_spec.rb +++ b/spec/requests/api/group_export_spec.rb @@ -64,6 +64,23 @@ RSpec.describe API::GroupExport do expect(response).to have_gitlab_http_status(:not_found) end end + + context 'when object is not present' do + let(:other_group) { create(:group, :with_export) } + let(:other_download_path) { "/groups/#{other_group.id}/export/download" } + + before do + other_group.add_owner(user) + other_group.export_file.file.delete + end + + it 'returns 404' do + get api(other_download_path, user) + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('The group export file is not available yet') + end + end end context 'when export file does not exist' do @@ -215,7 +232,7 @@ RSpec.describe API::GroupExport do context 'when export file exists' do it 'downloads exported group archive' do - upload.update!(export_file: fixture_file_upload('spec/fixtures/bulk_imports/labels.ndjson.gz')) + upload.update!(export_file: fixture_file_upload('spec/fixtures/bulk_imports/gz/labels.ndjson.gz')) get api(download_path, user) diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb index 900ffe6dfc7..c677e68b285 100644 --- a/spec/requests/api/group_labels_spec.rb +++ b/spec/requests/api/group_labels_spec.rb @@ -290,7 +290,7 @@ RSpec.describe API::GroupLabels do put api("/groups/#{group.id}/labels", user), params: { name: group_label1.name } expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['error']).to eq('new_name, color, description, remove_on_close are missing, '\ + expect(json_response['error']).to eq('new_name, color, description are missing, '\ 'at least one parameter must be provided') end end @@ -337,7 +337,7 @@ RSpec.describe API::GroupLabels do put api("/groups/#{group.id}/labels/#{valid_group_label_title_1_esc}", user) expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['error']).to eq('new_name, color, description, remove_on_close are missing, '\ + expect(json_response['error']).to eq('new_name, color, description are missing, '\ 'at least one parameter must be provided') end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 1c359b6e50f..0a47b93773b 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -506,7 +506,7 @@ RSpec.describe API::Groups do end it "does not return a non existing group" do - get api("/groups/1328", user1) + get api("/groups/#{non_existing_record_id}", user1) expect(response).to have_gitlab_http_status(:not_found) end @@ -586,7 +586,7 @@ RSpec.describe API::Groups do end it "does not return a non existing group" do - get api("/groups/1328", admin) + get api("/groups/#{non_existing_record_id}", admin) expect(response).to have_gitlab_http_status(:not_found) end @@ -631,30 +631,11 @@ RSpec.describe API::Groups do end end - context 'when limiting feature is enabled' do - before do - stub_feature_flags(limit_projects_in_groups_api: true) - end - - it 'limits projects and shared_projects' do - get api("/groups/#{group1.id}") - - expect(json_response['projects'].count).to eq(limit) - expect(json_response['shared_projects'].count).to eq(limit) - end - end - - context 'when limiting feature is not enabled' do - before do - stub_feature_flags(limit_projects_in_groups_api: false) - end - - it 'does not limit projects and shared_projects' do - get api("/groups/#{group1.id}") + it 'limits projects and shared_projects' do + get api("/groups/#{group1.id}") - expect(json_response['projects'].count).to eq(3) - expect(json_response['shared_projects'].count).to eq(3) - end + expect(json_response['projects'].count).to eq(limit) + expect(json_response['shared_projects'].count).to eq(limit) end end end @@ -748,7 +729,7 @@ RSpec.describe API::Groups do end it 'returns 404 for a non existing group' do - put api('/groups/1328', user1), params: { name: new_group_name } + put api("/groups/#{non_existing_record_id}", user1), params: { name: new_group_name } expect(response).to have_gitlab_http_status(:not_found) end @@ -973,7 +954,7 @@ RSpec.describe API::Groups do end it "does not return a non existing group" do - get api("/groups/1328/projects", user1) + get api("/groups/#{non_existing_record_id}/projects", user1) expect(response).to have_gitlab_http_status(:not_found) end @@ -1027,7 +1008,7 @@ RSpec.describe API::Groups do end it "does not return a non existing group" do - get api("/groups/1328/projects", admin) + get api("/groups/#{non_existing_record_id}/projects", admin) expect(response).to have_gitlab_http_status(:not_found) end @@ -1686,7 +1667,7 @@ RSpec.describe API::Groups do end it "does not remove a non existing group" do - delete api("/groups/1328", user1) + delete api("/groups/#{non_existing_record_id}", user1) expect(response).to have_gitlab_http_status(:not_found) end @@ -1706,7 +1687,7 @@ RSpec.describe API::Groups do end it "does not remove a non existing group" do - delete api("/groups/1328", admin) + delete api("/groups/#{non_existing_record_id}", admin) expect(response).to have_gitlab_http_status(:not_found) end diff --git a/spec/requests/api/helm_packages_spec.rb b/spec/requests/api/helm_packages_spec.rb new file mode 100644 index 00000000000..5871c0a5d5b --- /dev/null +++ b/spec/requests/api/helm_packages_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe API::HelmPackages do + include_context 'helm api setup' + + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:project) { create(:project, :public) } + 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) } + + describe 'GET /api/v4/projects/:id/packages/helm/:channel/charts/:file_name.tgz' do + let_it_be(:package) { create(:helm_package, project: project) } + + let(:channel) { package.package_files.first.helm_channel } + + let(:url) { "/projects/#{project.id}/packages/helm/#{channel}/charts/#{package.name}-#{package.version}.tgz" } + + subject { get api(url) } + + context 'with valid project' do + where(:visibility, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + :public | :developer | true | true | 'process helm download content request' | :success + :public | :guest | true | true | 'process helm download content request' | :success + :public | :developer | true | false | 'rejects helm packages access' | :unauthorized + :public | :guest | true | false | 'rejects helm packages access' | :unauthorized + :public | :developer | false | true | 'process helm download content request' | :success + :public | :guest | false | true | 'process helm download content request' | :success + :public | :developer | false | false | 'rejects helm packages access' | :unauthorized + :public | :guest | false | false | 'rejects helm packages access' | :unauthorized + :public | :anonymous | false | true | 'process helm download content request' | :success + :private | :developer | true | true | 'process helm download content request' | :success + :private | :guest | true | true | 'rejects helm packages access' | :forbidden + :private | :developer | true | false | 'rejects helm packages access' | :unauthorized + :private | :guest | true | false | 'rejects helm packages access' | :unauthorized + :private | :developer | false | true | 'rejects helm packages access' | :not_found + :private | :guest | false | true | 'rejects helm packages access' | :not_found + :private | :developer | false | false | 'rejects helm packages access' | :unauthorized + :private | :guest | false | false | 'rejects helm packages access' | :unauthorized + :private | :anonymous | false | true | 'rejects helm packages access' | :unauthorized + end + + 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) { { project: project, namespace: project.namespace } } + + subject { get api(url), headers: headers } + + before do + project.update!(visibility: visibility.to_s) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + it_behaves_like 'deploy token for package GET requests' + + it_behaves_like 'rejects helm access with unknown project id' + end +end diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index 6bedd43e5c4..631698554f9 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -341,9 +341,9 @@ RSpec.describe API::Internal::Base do end describe "GET /internal/authorized_keys" do - context "using an existing key's fingerprint" do + context "using an existing key" do it "finds the key" do - get(api('/internal/authorized_keys'), params: { fingerprint: key.fingerprint, secret_token: secret_token }) + get(api('/internal/authorized_keys'), params: { key: key.key.split[1], secret_token: secret_token }) expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq(key.id) @@ -351,58 +351,23 @@ RSpec.describe API::Internal::Base do end it 'exposes the comment of the key as a simple identifier of username + hostname' do - get(api('/internal/authorized_keys'), params: { fingerprint: key.fingerprint, secret_token: secret_token }) + get(api('/internal/authorized_keys'), params: { key: key.key.split[1], secret_token: secret_token }) expect(response).to have_gitlab_http_status(:ok) expect(json_response['key']).to include("#{key.user_name} (#{Gitlab.config.gitlab.host})") end end - context "non existing key's fingerprint" do - it "returns 404" do - get(api('/internal/authorized_keys'), params: { fingerprint: "no:t-:va:li:d0", secret_token: secret_token }) + it "returns 404 with a partial key" do + get(api('/internal/authorized_keys'), params: { key: key.key.split[1][0...-3], secret_token: secret_token }) - expect(response).to have_gitlab_http_status(:not_found) - end + expect(response).to have_gitlab_http_status(:not_found) end - context "using a partial fingerprint" do - it "returns 404" do - get(api('/internal/authorized_keys'), params: { fingerprint: "#{key.fingerprint[0..5]}%", secret_token: secret_token }) - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - context "sending the key" do - context "using an existing key" do - it "finds the key" do - get(api('/internal/authorized_keys'), params: { key: key.key.split[1], secret_token: secret_token }) - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['id']).to eq(key.id) - expect(json_response['key'].split[1]).to eq(key.key.split[1]) - end - - it 'exposes the comment of the key as a simple identifier of username + hostname' do - get(api('/internal/authorized_keys'), params: { fingerprint: key.fingerprint, secret_token: secret_token }) + it "returns 404 with an not valid base64 string" do + get(api('/internal/authorized_keys'), params: { key: "whatever!", secret_token: secret_token }) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['key']).to include("#{key.user_name} (#{Gitlab.config.gitlab.host})") - end - end - - it "returns 404 with a partial key" do - get(api('/internal/authorized_keys'), params: { key: key.key.split[1][0...-3], secret_token: secret_token }) - - expect(response).to have_gitlab_http_status(:not_found) - end - - it "returns 404 with an not valid base64 string" do - get(api('/internal/authorized_keys'), params: { key: "whatever!", secret_token: secret_token }) - - expect(response).to have_gitlab_http_status(:not_found) - end + expect(response).to have_gitlab_http_status(:not_found) end end diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb index b0e54055854..f9f03c9e55c 100644 --- a/spec/requests/api/invitations_spec.rb +++ b/spec/requests/api/invitations_spec.rb @@ -61,7 +61,7 @@ RSpec.describe API::Invitations do context 'and new member is already a requester' do it 'does not transform the requester into a proper member' do expect do - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: access_requester.email, access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(:created) @@ -71,7 +71,7 @@ RSpec.describe API::Invitations do it 'invites a new member' do expect do - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: email, access_level: Member::DEVELOPER } expect(response).to have_gitlab_http_status(:created) @@ -82,7 +82,7 @@ RSpec.describe API::Invitations do expect do email_list = [email, email2].join(',') - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: email_list, access_level: Member::DEVELOPER } expect(response).to have_gitlab_http_status(:created) @@ -98,7 +98,7 @@ RSpec.describe API::Invitations do project.update!(group: group) parent.add_developer(stranger) - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: stranger.email, access_level: Member::REPORTER } expect(response).to have_gitlab_http_status(:created) @@ -113,7 +113,7 @@ RSpec.describe API::Invitations do project.update!(group: group) parent.add_developer(stranger) - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: stranger.email, access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(:created) @@ -122,7 +122,7 @@ RSpec.describe API::Invitations do context 'access expiry date' do subject do - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: email, access_level: Member::DEVELOPER, expires_at: expires_at } end @@ -152,8 +152,36 @@ RSpec.describe API::Invitations do end end + context 'with invite_source considerations', :snowplow do + let(:params) { { email: email, access_level: Member::DEVELOPER } } + + it 'tracks the invite source as api' do + post invitations_url(source, maintainer), params: params + + expect_snowplow_event( + category: 'Members::InviteService', + action: 'create_member', + label: 'invitations-api', + property: 'net_new_user', + user: maintainer + ) + end + + it 'tracks the invite source from params' do + post invitations_url(source, maintainer), params: params.merge(invite_source: '_invite_source_') + + expect_snowplow_event( + category: 'Members::InviteService', + action: 'create_member', + label: '_invite_source_', + property: 'net_new_user', + user: maintainer + ) + end + end + it "returns a message if member already exists" do - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: developer.email, access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(:created) @@ -161,7 +189,7 @@ RSpec.describe API::Invitations do end it 'returns 404 when the email is not valid' do - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: '', access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(:created) @@ -169,7 +197,7 @@ RSpec.describe API::Invitations do end it 'returns 404 when the email list is not a valid format' do - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: 'email1@example.com,not-an-email', access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(:bad_request) @@ -177,14 +205,14 @@ RSpec.describe API::Invitations do end it 'returns 400 when email is not given' do - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { access_level: Member::MAINTAINER } expect(response).to have_gitlab_http_status(:bad_request) end it 'returns 400 when access_level is not given' do - post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), + post invitations_url(source, maintainer), params: { email: email } expect(response).to have_gitlab_http_status(:bad_request) diff --git a/spec/requests/api/issues/put_projects_issues_spec.rb b/spec/requests/api/issues/put_projects_issues_spec.rb index 38c080059c4..dac721cbea0 100644 --- a/spec/requests/api/issues/put_projects_issues_spec.rb +++ b/spec/requests/api/issues/put_projects_issues_spec.rb @@ -402,17 +402,6 @@ RSpec.describe API::Issues do expect(response).to have_gitlab_http_status(:ok) expect(json_response['state']).to eq 'opened' end - - it 'removes labels marked to be removed on issue closed' do - removable_label = create(:label, project: project, remove_on_close: true) - create(:label_link, target: issue, label: removable_label) - - put api_for_user, params: { state_event: 'close' } - - expect(issue.reload.label_ids).not_to include(removable_label.id) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['state']).to eq 'closed' - end end describe 'PUT /projects/:id/issues/:issue_iid to update updated_at param' do diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index f2ceedf6dbd..26377c40b73 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -57,7 +57,7 @@ RSpec.describe API::Labels do put_labels_api(route_type, user, spec_params) expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['error']).to eq('new_name, color, description, priority, remove_on_close are missing, '\ + expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\ 'at least one parameter must be provided') end @@ -112,14 +112,6 @@ RSpec.describe API::Labels do expect(json_response['id']).to eq(expected_response_label_id) expect(json_response['priority']).to eq(10) end - - it "returns 200 if remove_on_close is changed (#{route_type} route)" do - put_labels_api(route_type, user, spec_params, remove_on_close: true) - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['id']).to eq(expected_response_label_id) - expect(json_response['remove_on_close']).to eq(true) - end end it 'returns 200 if a priority is removed (deprecated route)' do @@ -309,8 +301,7 @@ RSpec.describe API::Labels do name: valid_label_title_2, color: '#FFAABB', description: 'test', - priority: 2, - remove_on_close: true + priority: 2 } expect(response).to have_gitlab_http_status(:created) @@ -318,7 +309,6 @@ RSpec.describe API::Labels do expect(json_response['color']).to eq('#FFAABB') expect(json_response['description']).to eq('test') expect(json_response['priority']).to eq(2) - expect(json_response['remove_on_close']).to eq(true) end it 'returns created label when only required params' do diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb index 4fc5fcf8282..d9f11b19e6e 100644 --- a/spec/requests/api/maven_packages_spec.rb +++ b/spec/requests/api/maven_packages_spec.rb @@ -7,7 +7,7 @@ RSpec.describe API::MavenPackages do include_context 'workhorse headers' let_it_be_with_refind(:package_settings) { create(:namespace_package_setting, :group) } - let_it_be(:group) { package_settings.namespace } + let_it_be_with_refind(:group) { package_settings.namespace } let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, :public, namespace: group) } let_it_be(:package, reload: true) { create(:maven_package, project: project, name: project.full_path) } @@ -15,12 +15,13 @@ RSpec.describe API::MavenPackages do let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first } let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } - let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) } + let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running, project: project) } 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(: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, user: user } } let(:package_name) { 'com/example/my-app' } let(:headers) { workhorse_headers } let(:headers_with_token) { headers.merge('Private-Token' => personal_access_token.token) } @@ -39,22 +40,73 @@ RSpec.describe API::MavenPackages do project.add_developer(user) end + shared_examples 'handling groups and subgroups for' do |shared_example_name, visibilities: %i[public]| + context 'within a group' do + visibilities.each do |visibility| + context "that is #{visibility}" do + before do + group.update!(visibility_level: Gitlab::VisibilityLevel.level_value(visibility.to_s)) + end + + it_behaves_like shared_example_name + end + end + end + + context 'within a subgroup' do + let_it_be_with_reload(:subgroup) { create(:group, parent: group) } + + before do + move_project_to_namespace(subgroup) + end + + visibilities.each do |visibility| + context "that is #{visibility}" do + before do + subgroup.update!(visibility_level: Gitlab::VisibilityLevel.level_value(visibility.to_s)) + group.update!(visibility_level: Gitlab::VisibilityLevel.level_value(visibility.to_s)) + end + + it_behaves_like shared_example_name + end + end + end + end + + shared_examples 'handling groups, subgroups and user namespaces for' do |shared_example_name, visibilities: %i[public]| + it_behaves_like 'handling groups and subgroups for', shared_example_name, visibilities: visibilities + + context 'within a user namespace' do + before do + move_project_to_namespace(user.namespace) + end + + visibilities.each do |visibility| + context "that is #{visibility}" do + before do + user.namespace.update!(visibility_level: Gitlab::VisibilityLevel.level_value(visibility.to_s)) + end + + it_behaves_like shared_example_name + end + end + end + end + shared_examples 'tracking the file download event' do context 'with jar file' do let_it_be(:package_file) { jar_file } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + it_behaves_like 'a package tracking event', described_class.name, 'pull_package' end end shared_examples 'rejecting the request for non existing maven path' do |expected_status: :not_found| - before do - if Feature.enabled?(:check_maven_path_first, default_enabled: :yaml) - expect(::Packages::Maven::PackageFinder).not_to receive(:new) - end - end - it 'rejects the request' do + expect(::Packages::Maven::PackageFinder).not_to receive(:new) + subject expect(response).to have_gitlab_http_status(expected_status) @@ -166,10 +218,10 @@ RSpec.describe API::MavenPackages do end describe 'GET /api/v4/packages/maven/*path/:file_name' do - shared_examples 'handling all conditions' do - context 'a public project' do - subject { download_file(file_name: package_file.file_name) } + context 'a public project' do + subject { download_file(file_name: package_file.file_name) } + shared_examples 'getting a file' do it_behaves_like 'tracking the file download event' it 'returns the file' do @@ -194,14 +246,18 @@ RSpec.describe API::MavenPackages do end end - context 'internal project' do - before do - project.team.truncate - project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) - end + it_behaves_like 'handling groups, subgroups and user namespaces for', 'getting a file' + end - subject { download_file_with_token(file_name: package_file.file_name) } + context 'internal project' do + before do + project.team.truncate + project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + end + subject { download_file_with_token(file_name: package_file.file_name) } + + shared_examples 'getting a file' do it_behaves_like 'tracking the file download event' it 'returns the file' do @@ -228,13 +284,17 @@ RSpec.describe API::MavenPackages do end end - context 'private project' do - subject { download_file_with_token(file_name: package_file.file_name) } + it_behaves_like 'handling groups, subgroups and user namespaces for', 'getting a file', visibilities: %i[public internal] + end - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - end + context 'private project' do + subject { download_file_with_token(file_name: package_file.file_name) } + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + shared_examples 'getting a file' do it_behaves_like 'tracking the file download event' it 'returns the file' do @@ -245,11 +305,13 @@ RSpec.describe API::MavenPackages do end it 'denies download when not enough permissions' do - project.add_guest(user) + unless project.root_namespace == user.namespace + project.add_guest(user) - subject + subject - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) + end end it 'denies download when no private token' do @@ -286,33 +348,19 @@ RSpec.describe API::MavenPackages do end end - context 'project name is different from a package name' do - before do - maven_metadatum.update!(path: "wrong_name/#{package.version}") - end - - it 'rejects request' do - download_file(file_name: package_file.file_name) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end + it_behaves_like 'handling groups, subgroups and user namespaces for', 'getting a file', visibilities: %i[public internal private] end - context 'with check_maven_path_first enabled' do + context 'project name is different from a package name' do before do - stub_feature_flags(check_maven_path_first: true) + maven_metadatum.update!(path: "wrong_name/#{package.version}") end - it_behaves_like 'handling all conditions' - end + it 'rejects request' do + download_file(file_name: package_file.file_name) - context 'with check_maven_path_first disabled' do - before do - stub_feature_flags(check_maven_path_first: false) + expect(response).to have_gitlab_http_status(:forbidden) end - - it_behaves_like 'handling all conditions' end def download_file(file_name:, params: {}, request_headers: headers, path: maven_metadatum.path) @@ -328,14 +376,16 @@ RSpec.describe API::MavenPackages do let(:path) { package.maven_metadatum.path } let(:url) { "/packages/maven/#{path}/#{package_file.file_name}" } - it_behaves_like 'processing HEAD requests', instance_level: true + shared_examples 'heading a file' do + it_behaves_like 'processing HEAD requests', instance_level: true + end context 'with check_maven_path_first enabled' do before do stub_feature_flags(check_maven_path_first: true) end - it_behaves_like 'processing HEAD requests', instance_level: true + it_behaves_like 'handling groups, subgroups and user namespaces for', 'heading a file' end context 'with check_maven_path_first disabled' do @@ -343,7 +393,7 @@ RSpec.describe API::MavenPackages do stub_feature_flags(check_maven_path_first: false) end - it_behaves_like 'processing HEAD requests', instance_level: true + it_behaves_like 'handling groups, subgroups and user namespaces for', 'heading a file' end end @@ -353,10 +403,10 @@ RSpec.describe API::MavenPackages do group.add_developer(user) end - shared_examples 'handling all conditions' do - context 'a public project' do - subject { download_file(file_name: package_file.file_name) } + context 'a public project' do + subject { download_file(file_name: package_file.file_name) } + shared_examples 'getting a file for a group' do it_behaves_like 'tracking the file download event' it 'returns the file' do @@ -381,14 +431,18 @@ RSpec.describe API::MavenPackages do end end - context 'internal project' do - before do - group.group_member(user).destroy! - project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) - end + it_behaves_like 'handling groups and subgroups for', 'getting a file for a group' + end + + context 'internal project' do + before do + group.group_member(user).destroy! + project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + end - subject { download_file_with_token(file_name: package_file.file_name) } + subject { download_file_with_token(file_name: package_file.file_name) } + shared_examples 'getting a file for a group' do it_behaves_like 'tracking the file download event' it 'returns the file' do @@ -415,13 +469,17 @@ RSpec.describe API::MavenPackages do end end - context 'private project' do - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - end + it_behaves_like 'handling groups and subgroups for', 'getting a file for a group', visibilities: %i[internal public] + end - subject { download_file_with_token(file_name: package_file.file_name) } + context 'private project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + subject { download_file_with_token(file_name: package_file.file_name) } + shared_examples 'getting a file for a group' do it_behaves_like 'tracking the file download event' it 'returns the file' do @@ -480,101 +538,87 @@ RSpec.describe API::MavenPackages do it_behaves_like 'rejecting the request for non existing maven path' end end + end - context 'with a reporter from a subgroup accessing the root group' do - let_it_be(:root_group) { create(:group, :private) } - let_it_be(:group) { create(:group, :private, parent: root_group) } + it_behaves_like 'handling groups and subgroups for', 'getting a file for a group', visibilities: %i[private internal public] - subject { download_file_with_token(file_name: package_file.file_name, request_headers: headers_with_token, group_id: root_group.id) } + context 'with a reporter from a subgroup accessing the root group' do + let_it_be(:root_group) { create(:group, :private) } + let_it_be(:group) { create(:group, :private, parent: root_group) } - before do - project.update!(namespace: group) - group.add_reporter(user) - end + subject { download_file_with_token(file_name: package_file.file_name, request_headers: headers_with_token, group_id: root_group.id) } - it 'returns the file' do - subject + before do + project.update!(namespace: group) + group.add_reporter(user) + end - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it 'returns the file' do + subject - context 'with a non existing maven path' do - subject { download_file_with_token(file_name: package_file.file_name, path: 'foo/bar/1.2.3', request_headers: headers_with_token, group_id: root_group.id) } + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type).to eq('application/octet-stream') + end - it_behaves_like 'rejecting the request for non existing maven path' - end + context 'with a non existing maven path' do + subject { download_file_with_token(file_name: package_file.file_name, path: 'foo/bar/1.2.3', request_headers: headers_with_token, group_id: root_group.id) } + + it_behaves_like 'rejecting the request for non existing maven path' end end + end - context 'maven metadata file' do - let_it_be(:sub_group1) { create(:group, parent: group) } - let_it_be(:sub_group2) { create(:group, parent: group) } - let_it_be(:project1) { create(:project, :private, group: sub_group1) } - let_it_be(:project2) { create(:project, :private, group: sub_group2) } - let_it_be(:project3) { create(:project, :private, group: sub_group1) } - let_it_be(:package_name) { 'foo' } - let_it_be(:package1) { create(:maven_package, project: project1, name: package_name, version: nil) } - let_it_be(:package_file1) { create(:package_file, :xml, package: package1, file_name: 'maven-metadata.xml') } - let_it_be(:package2) { create(:maven_package, project: project2, name: package_name, version: nil) } - let_it_be(:package_file2) { create(:package_file, :xml, package: package2, file_name: 'maven-metadata.xml') } - let_it_be(:package3) { create(:maven_package, project: project3, name: package_name, version: nil) } - let_it_be(:package_file3) { create(:package_file, :xml, package: package3, file_name: 'maven-metadata.xml') } + context 'maven metadata file' do + let_it_be(:sub_group1) { create(:group, parent: group) } + let_it_be(:sub_group2) { create(:group, parent: group) } + let_it_be(:project1) { create(:project, :private, group: sub_group1) } + let_it_be(:project2) { create(:project, :private, group: sub_group2) } + let_it_be(:project3) { create(:project, :private, group: sub_group1) } + let_it_be(:package_name) { 'foo' } + let_it_be(:package1) { create(:maven_package, project: project1, name: package_name, version: nil) } + let_it_be(:package_file1) { create(:package_file, :xml, package: package1, file_name: 'maven-metadata.xml') } + let_it_be(:package2) { create(:maven_package, project: project2, name: package_name, version: nil) } + let_it_be(:package_file2) { create(:package_file, :xml, package: package2, file_name: 'maven-metadata.xml') } + let_it_be(:package3) { create(:maven_package, project: project3, name: package_name, version: nil) } + let_it_be(:package_file3) { create(:package_file, :xml, package: package3, file_name: 'maven-metadata.xml') } - let(:maven_metadatum) { package3.maven_metadatum } + let(:maven_metadatum) { package3.maven_metadatum } - subject { download_file_with_token(file_name: package_file3.file_name) } + subject { download_file_with_token(file_name: package_file3.file_name) } - before do - sub_group1.add_developer(user) - sub_group2.add_developer(user) - # the package with the most recently published file should be returned - create(:package_file, :xml, package: package2) - end + before do + sub_group1.add_developer(user) + sub_group2.add_developer(user) + # the package with the most recently published file should be returned + create(:package_file, :xml, package: package2) + end - context 'in multiple versionless packages' do - it 'downloads the file' do - expect(::Packages::PackageFileFinder) - .to receive(:new).with(package2, 'maven-metadata.xml').and_call_original + context 'in multiple versionless packages' do + it 'downloads the file' do + expect(::Packages::PackageFileFinder) + .to receive(:new).with(package2, 'maven-metadata.xml').and_call_original - subject - end + subject end + end - context 'in multiple snapshot packages' do - before do - version = '1.0.0-SNAPSHOT' - [package1, package2, package3].each do |pkg| - pkg.update!(version: version) - - pkg.maven_metadatum.update!(path: "#{pkg.name}/#{pkg.version}") - end - end - - it 'downloads the file' do - expect(::Packages::PackageFileFinder) - .to receive(:new).with(package3, 'maven-metadata.xml').and_call_original + context 'in multiple snapshot packages' do + before do + version = '1.0.0-SNAPSHOT' + [package1, package2, package3].each do |pkg| + pkg.update!(version: version) - subject + pkg.maven_metadatum.update!(path: "#{pkg.name}/#{pkg.version}") end end - end - end - context 'with check_maven_path_first enabled' do - before do - stub_feature_flags(check_maven_path_first: true) - end - - it_behaves_like 'handling all conditions' - end + it 'downloads the file' do + expect(::Packages::PackageFileFinder) + .to receive(:new).with(package3, 'maven-metadata.xml').and_call_original - context 'with check_maven_path_first disabled' do - before do - stub_feature_flags(check_maven_path_first: false) + subject + end end - - it_behaves_like 'handling all conditions' end def download_file(file_name:, params: {}, request_headers: headers, path: maven_metadatum.path, group_id: group.id) @@ -595,7 +639,7 @@ RSpec.describe API::MavenPackages do stub_feature_flags(check_maven_path_first: true) end - it_behaves_like 'processing HEAD requests' + it_behaves_like 'handling groups and subgroups for', 'processing HEAD requests' end context 'with check_maven_path_first disabled' do @@ -603,95 +647,77 @@ RSpec.describe API::MavenPackages do stub_feature_flags(check_maven_path_first: false) end - it_behaves_like 'processing HEAD requests' + it_behaves_like 'handling groups and subgroups for', 'processing HEAD requests' end end describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do - shared_examples 'handling all conditions' do - context 'a public project' do - subject { download_file(file_name: package_file.file_name) } - - it_behaves_like 'tracking the file download event' - - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + context 'a public project' do + subject { download_file(file_name: package_file.file_name) } - it 'returns sha1 of the file' do - download_file(file_name: package_file.file_name + '.sha1') + it_behaves_like 'tracking the file download event' - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('text/plain') - expect(response.body).to eq(package_file.file_sha1) - end - - context 'with a non existing maven path' do - subject { download_file(file_name: package_file.file_name, path: 'foo/bar/1.2.3') } + it 'returns the file' do + subject - it_behaves_like 'rejecting the request for non existing maven path' - end + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type).to eq('application/octet-stream') end - context 'private project' do - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) - end + it 'returns sha1 of the file' do + download_file(file_name: package_file.file_name + '.sha1') - subject { download_file_with_token(file_name: package_file.file_name) } + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type).to eq('text/plain') + expect(response.body).to eq(package_file.file_sha1) + end - it_behaves_like 'tracking the file download event' + context 'with a non existing maven path' do + subject { download_file(file_name: package_file.file_name, path: 'foo/bar/1.2.3') } - it 'returns the file' do - subject + it_behaves_like 'rejecting the request for non existing maven path' + end + end - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + context 'private project' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end - it 'denies download when not enough permissions' do - project.add_guest(user) + subject { download_file_with_token(file_name: package_file.file_name) } - subject + it_behaves_like 'tracking the file download event' - expect(response).to have_gitlab_http_status(:forbidden) - end + it 'returns the file' do + subject - it 'denies download when no private token' do - download_file(file_name: package_file.file_name) + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type).to eq('application/octet-stream') + end - expect(response).to have_gitlab_http_status(:not_found) - end + it 'denies download when not enough permissions' do + project.add_guest(user) - it_behaves_like 'downloads with a job token' + subject - it_behaves_like 'downloads with a deploy token' + expect(response).to have_gitlab_http_status(:forbidden) + end - context 'with a non existing maven path' do - subject { download_file_with_token(file_name: package_file.file_name, path: 'foo/bar/1.2.3') } + it 'denies download when no private token' do + download_file(file_name: package_file.file_name) - it_behaves_like 'rejecting the request for non existing maven path' - end + expect(response).to have_gitlab_http_status(:not_found) end - end - context 'with check_maven_path_first enabled' do - before do - stub_feature_flags(check_maven_path_first: true) - end + it_behaves_like 'downloads with a job token' - it_behaves_like 'handling all conditions' - end + it_behaves_like 'downloads with a deploy token' - context 'with check_maven_path_first disabled' do - before do - stub_feature_flags(check_maven_path_first: false) - end + context 'with a non existing maven path' do + subject { download_file_with_token(file_name: package_file.file_name, path: 'foo/bar/1.2.3') } - it_behaves_like 'handling all conditions' + it_behaves_like 'rejecting the request for non existing maven path' + end end def download_file(file_name:, params: {}, request_headers: headers, path: maven_metadatum.path) @@ -1020,4 +1046,10 @@ RSpec.describe API::MavenPackages do upload_file(params: params, request_headers: request_headers, file_extension: file_extension) end end + + def move_project_to_namespace(namespace) + project.update!(namespace: namespace) + package.update!(name: project.full_path) + maven_metadatum.update!(path: "#{package.name}/#{package.version}") + end end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index d488aee0c10..cac1b95e854 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -255,11 +255,41 @@ RSpec.describe API::Members do expect(json_response['access_level']).to eq(Member::DEVELOPER) end - describe 'executes the Members::CreateService for multiple user_ids' do + context 'with invite_source considerations', :snowplow do + let(:params) { { user_id: stranger.id, access_level: Member::DEVELOPER } } + + it 'tracks the invite source as api' do + post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), + params: params + + expect_snowplow_event( + category: 'Members::CreateService', + action: 'create_member', + label: 'members-api', + property: 'existing_user', + user: maintainer + ) + end + + it 'tracks the invite source from params' do + post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), + params: params.merge(invite_source: '_invite_source_') + + expect_snowplow_event( + category: 'Members::CreateService', + action: 'create_member', + label: '_invite_source_', + property: 'existing_user', + user: maintainer + ) + end + end + + context 'when executing the Members::CreateService for multiple user_ids' do + let(:user_ids) { [stranger.id, access_requester.id].join(',') } + it 'returns success when it successfully create all members' do expect do - user_ids = [stranger.id, access_requester.id].join(',') - post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), params: { user_id: user_ids, access_level: Member::DEVELOPER } @@ -270,8 +300,6 @@ RSpec.describe API::Members do it 'returns the error message if there was an error adding members to group' do error_message = 'Unable to find User ID' - user_ids = [stranger.id, access_requester.id].join(',') - allow_next_instance_of(::Members::CreateService) do |service| expect(service).to receive(:execute).and_return({ status: :error, message: error_message }) end @@ -283,6 +311,36 @@ RSpec.describe API::Members do expect(json_response['status']).to eq('error') expect(json_response['message']).to eq(error_message) end + + context 'with invite_source considerations', :snowplow do + let(:params) { { user_id: user_ids, access_level: Member::DEVELOPER } } + + it 'tracks the invite source as api' do + post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), + params: params + + expect_snowplow_event( + category: 'Members::CreateService', + action: 'create_member', + label: 'members-api', + property: 'existing_user', + user: maintainer + ) + end + + it 'tracks the invite source from params' do + post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), + params: params.merge(invite_source: '_invite_source_') + + expect_snowplow_event( + category: 'Members::CreateService', + action: 'create_member', + label: '_invite_source_', + property: 'existing_user', + user: maintainer + ) + end + end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index a13db1bb414..038c3bc552a 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1186,7 +1186,8 @@ RSpec.describe API::MergeRequests do expect(json_response['downvotes']).to eq(1) expect(json_response['source_project_id']).to eq(merge_request.source_project.id) expect(json_response['target_project_id']).to eq(merge_request.target_project.id) - expect(json_response['work_in_progress']).to be_falsy + expect(json_response['draft']).to be false + expect(json_response['work_in_progress']).to be false expect(json_response['merge_when_pipeline_succeeds']).to be_falsy expect(json_response['merge_status']).to eq('can_be_merged') expect(json_response['should_close_merge_request']).to be_falsy @@ -1329,29 +1330,30 @@ RSpec.describe API::MergeRequests do expect(response).to have_gitlab_http_status(:not_found) end - context 'Work in Progress' do - let!(:merge_request_wip) do + context 'Draft' do + let!(:merge_request_draft) do create(:merge_request, author: user, assignees: [user], source_project: project, target_project: project, - title: "WIP: Test", + title: "Draft: Test", created_at: base_time + 1.second ) end it "returns merge request" do - get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.iid}", user) + get api("/projects/#{project.id}/merge_requests/#{merge_request_draft.iid}", user) expect(response).to have_gitlab_http_status(:ok) + expect(json_response['draft']).to eq(true) expect(json_response['work_in_progress']).to eq(true) end end context 'when a merge request has more than the changes limit' do it "returns a string indicating that more changes were made" do - allow(Commit).to receive(:diff_hard_limit_files).and_return(5) + allow(Commit).to receive(:diff_max_files).and_return(5) merge_request_overflow = create(:merge_request, :simple, author: user, @@ -2174,6 +2176,12 @@ RSpec.describe API::MergeRequests do a_hash_including('name' => user2.name) ) end + + it 'creates appropriate system notes', :sidekiq_inline do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: params + + expect(merge_request.notes.system.last.note).to include("assigned to #{user2.to_reference}") + end end context 'when assignee_id=user2.id' do @@ -2193,6 +2201,27 @@ RSpec.describe API::MergeRequests do end end + context 'when assignee_id=0' do + let(:params) do + { + assignee_id: 0 + } + end + + it 'clears the assignees' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: params + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['assignees']).to be_empty + end + + it 'creates appropriate system notes', :sidekiq_inline do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: params + + expect(merge_request.notes.system.last.note).to include("unassigned #{user.to_reference}") + end + end + context 'when only assignee_ids are provided, and the list is empty' do let(:params) do { @@ -2495,8 +2524,8 @@ RSpec.describe API::MergeRequests do expect(json_response['message']).to eq('405 Method Not Allowed') end - it "returns 405 if merge_request is a work in progress" do - merge_request.update_attribute(:title, "WIP: #{merge_request.title}") + it "returns 405 if merge_request is a draft" do + merge_request.update_attribute(:title, "Draft: #{merge_request.title}") put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user) expect(response).to have_gitlab_http_status(:method_not_allowed) expect(json_response['message']).to eq('405 Method Not Allowed') diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb index 10271719a15..ab74da4bda4 100644 --- a/spec/requests/api/npm_project_packages_spec.rb +++ b/spec/requests/api/npm_project_packages_spec.rb @@ -71,12 +71,14 @@ RSpec.describe API::NpmProjectPackages do end context 'a public project' do + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + it_behaves_like 'successfully downloads the file' it_behaves_like 'a package tracking event', 'API::NpmPackages', 'pull_package' context 'with a job token for a different user' do let_it_be(:other_user) { create(:user) } - let_it_be_with_reload(:other_job) { create(:ci_build, :running, user: other_user) } + let_it_be_with_reload(:other_job) { create(:ci_build, :running, user: other_user, project: project) } let(:headers) { build_token_auth_header(other_job.token) } @@ -161,6 +163,7 @@ RSpec.describe API::NpmProjectPackages do context 'valid package record' do let(:params) { upload_params(package_name: package_name) } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } shared_examples 'handling upload with different authentications' do context 'with access token' do diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb index aefbc89dc3b..1b71f0f9de1 100644 --- a/spec/requests/api/nuget_group_packages_spec.rb +++ b/spec/requests/api/nuget_group_packages_spec.rb @@ -46,6 +46,7 @@ RSpec.describe API::NugetGroupPackages do let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: subgroup) } let(:target) { subgroup } + let(:snowplow_gitlab_standard_context) { { namespace: subgroup } } it_behaves_like 'handling all endpoints' @@ -57,6 +58,7 @@ RSpec.describe API::NugetGroupPackages do context 'a group' do let(:target) { group } + let(:snowplow_gitlab_standard_context) { { namespace: group } } 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 54fe0b985df..572736cfc86 100644 --- a/spec/requests/api/nuget_project_packages_spec.rb +++ b/spec/requests/api/nuget_project_packages_spec.rb @@ -16,6 +16,7 @@ RSpec.describe API::NugetProjectPackages do describe 'GET /api/v4/projects/:id/packages/nuget' do it_behaves_like 'handling nuget service requests' do let(:url) { "/projects/#{target.id}/packages/nuget/index.json" } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } end end @@ -34,6 +35,7 @@ RSpec.describe API::NugetProjectPackages do describe 'GET /api/v4/projects/:id/packages/nuget/query' do it_behaves_like 'handling nuget search requests' do let(:url) { "/projects/#{target.id}/packages/nuget/query?#{query_parameters.to_query}" } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } end end @@ -121,6 +123,7 @@ RSpec.describe API::NugetProjectPackages 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) { { project: project, namespace: project.namespace } } subject { get api(url), headers: headers } @@ -189,7 +192,7 @@ RSpec.describe API::NugetProjectPackages do it_behaves_like 'deploy token for package uploads' it_behaves_like 'job token for package uploads', authorize_endpoint: true do - let_it_be(:job) { create(:ci_build, :running, user: user) } + let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } end it_behaves_like 'rejects nuget access with unknown target id' @@ -244,6 +247,7 @@ RSpec.describe API::NugetProjectPackages 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) { { project: project, user: user, namespace: project.namespace } } before do update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false)) @@ -256,7 +260,7 @@ RSpec.describe API::NugetProjectPackages do it_behaves_like 'deploy token for package uploads' it_behaves_like 'job token for package uploads' do - let_it_be(:job) { create(:ci_build, :running, user: user) } + let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } end it_behaves_like 'rejects nuget access with unknown target id' diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml index d28442bd692..2932447f663 100644 --- a/spec/requests/api/project_attributes.yml +++ b/spec/requests/api/project_attributes.yml @@ -69,6 +69,7 @@ itself: # project - shared_with_groups - ssh_url_to_repo - tag_list + - topics - web_url build_auto_devops: # auto_devops @@ -86,13 +87,13 @@ ci_cd_settings: - id - project_id - group_runners_enabled - - keep_latest_artifact - merge_pipelines_enabled - merge_trains_enabled - auto_rollback_enabled remapped_attributes: default_git_depth: ci_default_git_depth forward_deployment_enabled: ci_forward_deployment_enabled + job_token_scope_enabled: ci_job_token_scope_enabled build_import_state: # import_state unexposed_attributes: @@ -139,7 +140,6 @@ project_setting: - project_id - push_rule_id - show_default_award_emojis - - squash_option - updated_at - cve_id_request_enabled - mr_default_target_self diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb index f3da99573fe..695d2c3fe2c 100644 --- a/spec/requests/api/project_container_repositories_spec.rb +++ b/spec/requests/api/project_container_repositories_spec.rb @@ -32,6 +32,8 @@ RSpec.describe API::ProjectContainerRepositories do let(:method) { :get } let(:params) { {} } + let(:snowplow_gitlab_standard_context) { { user: api_user, project: project, namespace: project.namespace } } + before_all do project.add_maintainer(maintainer) project.add_developer(developer) @@ -405,7 +407,7 @@ RSpec.describe API::ProjectContainerRepositories do subject expect(response).to have_gitlab_http_status(:ok) - expect_snowplow_event(category: described_class.name, action: 'delete_tag') + expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project, user: api_user, namespace: project.namespace) end end @@ -421,7 +423,7 @@ RSpec.describe API::ProjectContainerRepositories do subject expect(response).to have_gitlab_http_status(:ok) - expect_snowplow_event(category: described_class.name, action: 'delete_tag') + expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project, user: api_user, namespace: project.namespace) end end end diff --git a/spec/requests/api/project_debian_distributions_spec.rb b/spec/requests/api/project_debian_distributions_spec.rb new file mode 100644 index 00000000000..de7362758f7 --- /dev/null +++ b/spec/requests/api/project_debian_distributions_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe API::ProjectDebianDistributions do + include HttpBasicAuthHelpers + include WorkhorseHelpers + + include_context 'Debian repository shared context', :project, true do + describe 'POST projects/:id/debian_distributions' do + let(:method) { :post } + let(:url) { "/projects/#{container.id}/debian_distributions" } + let(:api_params) { { 'codename': 'my-codename' } } + + it_behaves_like 'Debian repository write endpoint', 'POST distribution request', :created, /^{.*"codename":"my-codename",.*"components":\["main"\],.*"architectures":\["all","amd64"\]/, authenticate_non_public: false + + context 'with invalid parameters' do + let(:api_params) { { codename: distribution.codename } } + + it_behaves_like 'Debian repository write endpoint', 'GET request', :bad_request, /^{"message":{"codename":\["has already been taken"\]}}$/, authenticate_non_public: false + end + end + + describe 'GET projects/:id/debian_distributions' do + let(:url) { "/projects/#{container.id}/debian_distributions" } + + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^\[{.*"codename":"existing-codename\",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/, authenticate_non_public: false + end + + describe 'GET projects/:id/debian_distributions/:codename' do + let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}" } + + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^{.*"codename":"existing-codename\",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/, authenticate_non_public: false + end + + describe 'PUT projects/:id/debian_distributions/:codename' do + let(:method) { :put } + let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}" } + let(:api_params) { { suite: 'my-suite' } } + + it_behaves_like 'Debian repository write endpoint', 'PUT distribution request', :success, /^{.*"codename":"existing-codename",.*"suite":"my-suite",/, authenticate_non_public: false + + context 'with invalid parameters' do + let(:api_params) { { suite: distribution.codename } } + + it_behaves_like 'Debian repository write endpoint', 'GET request', :bad_request, /^{"message":{"suite":\["has already been taken as Codename"\]}}$/, authenticate_non_public: false + end + end + + describe 'DELETE projects/:id/debian_distributions/:codename' do + let(:method) { :delete } + let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}" } + + it_behaves_like 'Debian repository maintainer write endpoint', 'DELETE distribution request', :success, /^{\"message\":\"202 Accepted\"}$/, authenticate_non_public: false + + context 'when destroy fails' do + before do + allow_next_found_instance_of(::Packages::Debian::ProjectDistribution) do |distribution| + expect(distribution).to receive(:destroy).and_return(false) + end + end + + it_behaves_like 'Debian repository maintainer write endpoint', 'GET request', :bad_request, /^{"message":"Failed to delete distribution"}$/, authenticate_non_public: false + end + end + end +end diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb index ac24aeee52c..06f4475ef79 100644 --- a/spec/requests/api/project_export_spec.rb +++ b/spec/requests/api/project_export_spec.rb @@ -196,6 +196,19 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do end end + context 'when export object is not present' do + before do + project_after_export.export_file.file.delete + end + + it 'returns 404' do + get api(download_path_export_action, user) + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('The project export file is not available yet') + end + end + context 'when upload complete' do before do project_after_export.remove_exports diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb index fb1aa65c08d..5886f293f41 100644 --- a/spec/requests/api/project_packages_spec.rb +++ b/spec/requests/api/project_packages_spec.rb @@ -40,10 +40,36 @@ RSpec.describe API::ProjectPackages do context 'with terraform module package' do let_it_be(:terraform_module_package) { create(:terraform_module_package, project: project) } - it 'filters out terraform module packages when no package_type filter is set' do - subject + context 'when no package_type filter is set' do + let(:params) { {} } + + it 'filters out terraform module packages' do + subject + + expect(json_response).not_to include(a_hash_including('package_type' => 'terraform_module')) + end + + it 'returns packages with the package registry web_path' do + subject + + expect(json_response).to include(a_hash_including('_links' => a_hash_including('web_path' => include('packages')))) + end + end + + context 'when package_type filter is set to terraform_module' do + let(:params) { { package_type: :terraform_module } } - expect(json_response).not_to include(a_hash_including('package_type' => 'terraform_module')) + it 'returns the terraform module package' do + subject + + expect(json_response).to include(a_hash_including('package_type' => 'terraform_module')) + end + + it 'returns the terraform module package with the infrastructure registry web_path' do + subject + + expect(json_response).to include(a_hash_including('_links' => a_hash_including('web_path' => include('infrastructure_registry')))) + end end end diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb index b40645ba2de..5b272121233 100644 --- a/spec/requests/api/project_repository_storage_moves_spec.rb +++ b/spec/requests/api/project_repository_storage_moves_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe API::ProjectRepositoryStorageMoves do it_behaves_like 'repository_storage_moves API', 'projects' do - let_it_be(:container) { create(:project, :repository).tap { |project| project.track_project_repository } } + let_it_be(:container) { create(:project, :repository) } let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, container: container) } let(:repository_storage_move_factory) { :project_repository_storage_move } let(:bulk_worker_klass) { Projects::ScheduleBulkRepositoryShardMovesWorker } diff --git a/spec/requests/api/project_statistics_spec.rb b/spec/requests/api/project_statistics_spec.rb index 5f0cac403aa..d314af0746a 100644 --- a/spec/requests/api/project_statistics_spec.rb +++ b/spec/requests/api/project_statistics_spec.rb @@ -3,11 +3,11 @@ require 'spec_helper' RSpec.describe API::ProjectStatistics do - let_it_be(:developer) { create(:user) } + let_it_be(:reporter) { create(:user) } let_it_be(:public_project) { create(:project, :public) } before do - public_project.add_developer(developer) + public_project.add_reporter(reporter) end describe 'GET /projects/:id/statistics' do @@ -19,7 +19,7 @@ RSpec.describe API::ProjectStatistics do let_it_be(:fetch_statistics_other_project) { create(:project_daily_statistic, project: create(:project), fetch_count: 29, date: 29.days.ago) } it 'returns the fetch statistics of the last 30 days' do - get api("/projects/#{public_project.id}/statistics", developer) + get api("/projects/#{public_project.id}/statistics", reporter) expect(response).to have_gitlab_http_status(:ok) fetches = json_response['fetches'] @@ -32,7 +32,7 @@ RSpec.describe API::ProjectStatistics do it 'excludes the fetch statistics older than 30 days' do create(:project_daily_statistic, fetch_count: 31, project: public_project, date: 30.days.ago) - get api("/projects/#{public_project.id}/statistics", developer) + get api("/projects/#{public_project.id}/statistics", reporter) expect(response).to have_gitlab_http_status(:ok) fetches = json_response['fetches'] @@ -41,7 +41,7 @@ RSpec.describe API::ProjectStatistics do expect(fetches['days'].last).to eq({ 'count' => fetch_statistics1.fetch_count, 'date' => fetch_statistics1.date.to_s }) end - it 'responds with 403 when the user is not a developer of the repository' do + it 'responds with 403 when the user is not a reporter of the repository' do guest = create(:user) public_project.add_guest(guest) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 7f804186bc7..e7e26c34a83 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -53,7 +53,7 @@ RSpec.describe API::Projects do let_it_be(:user2) { create(:user) } let_it_be(:user3) { create(:user) } let_it_be(:admin) { create(:admin) } - let_it_be(:project, reload: true) { create(:project, :repository, namespace: user.namespace) } + let_it_be(:project, reload: true) { create(:project, :repository, create_branch: 'something_else', namespace: user.namespace) } let_it_be(:project2, reload: true) { create(:project, namespace: user.namespace) } let_it_be(:project_member) { create(:project_member, :developer, user: user3, project: project) } let_it_be(:user4) { create(:user, username: 'user.with.dot') } @@ -109,6 +109,43 @@ RSpec.describe API::Projects do end end + shared_examples_for 'create project with default branch parameter' do + let(:params) { { name: 'Foo Project', initialize_with_readme: true, default_branch: default_branch } } + let(:default_branch) { 'main' } + + it 'creates project with provided default branch name' do + expect { request }.to change { Project.count }.by(1) + expect(response).to have_gitlab_http_status(:created) + + project = Project.find(json_response['id']) + expect(project.default_branch).to eq(default_branch) + end + + context 'when branch name is empty' do + let(:default_branch) { '' } + + it 'creates project with a default project branch name' do + expect { request }.to change { Project.count }.by(1) + expect(response).to have_gitlab_http_status(:created) + + project = Project.find(json_response['id']) + expect(project.default_branch).to eq('master') + end + end + + context 'when initialize with readme is not set' do + let(:params) { super().merge(initialize_with_readme: nil) } + + it 'creates project with a default project branch name' do + expect { request }.to change { Project.count }.by(1) + expect(response).to have_gitlab_http_status(:created) + + project = Project.find(json_response['id']) + expect(project.default_branch).to be_nil + end + end + end + describe 'GET /projects' do shared_examples_for 'projects response' do it 'returns an array of projects' do @@ -184,13 +221,40 @@ RSpec.describe API::Projects do end end - it 'includes the project labels as the tag_list' do + it 'includes correct value of container_registry_enabled', :aggregate_failures do + project.update_column(:container_registry_enabled, true) + project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED) + + get api('/projects', user) + project_response = json_response.find { |p| p['id'] == project.id } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(project_response['container_registry_enabled']).to eq(false) + end + + it 'reads projects.container_registry_enabled when read_container_registry_access_level is disabled' do + stub_feature_flags(read_container_registry_access_level: false) + + project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED) + project.update_column(:container_registry_enabled, true) + + get api('/projects', user) + project_response = json_response.find { |p| p['id'] == project.id } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(project_response['container_registry_enabled']).to eq(true) + end + + it 'includes project topics' do get api('/projects', user) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.first.keys).to include('tag_list') + expect(json_response.first.keys).to include('tag_list') # deprecated in favor of 'topics' + expect(json_response.first.keys).to include('topics') end it 'includes open_issues_count' do @@ -223,9 +287,9 @@ RSpec.describe API::Projects do expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count') end - context 'filter by topic (column tag_list)' do + context 'filter by topic (column topic_list)' do before do - project.update!(tag_list: %w(ruby javascript)) + project.update!(topic_list: %w(ruby javascript)) end it 'returns no projects' do @@ -742,10 +806,6 @@ RSpec.describe API::Projects do it 'includes a pagination header with link to the next page' do get api('/projects', current_user), params: params - expect(response.header).to include('Links') - expect(response.header['Links']).to include('pagination=keyset') - expect(response.header['Links']).to include("id_after=#{first_project_id}") - expect(response.header).to include('Link') expect(response.header['Link']).to include('pagination=keyset') expect(response.header['Link']).to include("id_after=#{first_project_id}") @@ -762,10 +822,6 @@ RSpec.describe API::Projects do it 'still includes a link if the end has reached and there is no more data after this page' do get api('/projects', current_user), params: params.merge(id_after: project2.id) - expect(response.header).to include('Links') - expect(response.header['Links']).to include('pagination=keyset') - expect(response.header['Links']).to include("id_after=#{project3.id}") - expect(response.header).to include('Link') expect(response.header['Link']).to include('pagination=keyset') expect(response.header['Link']).to include("id_after=#{project3.id}") @@ -774,7 +830,6 @@ RSpec.describe API::Projects do it 'does not include a next link when the page does not have any records' do get api('/projects', current_user), params: params.merge(id_after: Project.maximum(:id)) - expect(response.header).not_to include('Links') expect(response.header).not_to include('Link') end @@ -798,10 +853,6 @@ RSpec.describe API::Projects do it 'includes a pagination header with link to the next page' do get api('/projects', current_user), params: params - expect(response.header).to include('Links') - expect(response.header['Links']).to include('pagination=keyset') - expect(response.header['Links']).to include("id_before=#{last_project_id}") - expect(response.header).to include('Link') expect(response.header['Link']).to include('pagination=keyset') expect(response.header['Link']).to include("id_before=#{last_project_id}") @@ -828,11 +879,6 @@ RSpec.describe API::Projects do requests += 1 get api(url, current_user), params: params - links = response.header['Links'] - url = links&.match(/<[^>]+(\/projects\?[^>]+)>; rel="next"/) do |match| - match[1] - end - link = response.header['Link'] url = link&.match(/<[^>]+(\/projects\?[^>]+)>; rel="next"/) do |match| match[1] @@ -938,6 +984,10 @@ RSpec.describe API::Projects do expect(project.path).to eq('path-project-Foo') end + it_behaves_like 'create project with default branch parameter' do + let(:request) { post api('/projects', user), params: params } + end + it 'creates last project before reaching project limit' do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1) post api('/projects', user2), params: { name: 'foo' } @@ -1050,12 +1100,20 @@ RSpec.describe API::Projects do expect(json_response['readme_url']).to eql("#{Gitlab.config.gitlab.url}/#{json_response['namespace']['full_path']}/somewhere/-/blob/master/README.md") end - it 'sets tag list to a project' do + it 'sets tag list to a project (deprecated)' do project = attributes_for(:project, tag_list: %w[tagFirst tagSecond]) post api('/projects', user), params: project - expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond]) + expect(json_response['topics']).to eq(%w[tagFirst tagSecond]) + end + + it 'sets topics to a project' do + project = attributes_for(:project, topics: %w[topic1 topics2]) + + post api('/projects', user), params: project + + expect(json_response['topics']).to eq(%w[topic1 topics2]) end it 'uploads avatar for project a project' do @@ -1410,6 +1468,10 @@ RSpec.describe API::Projects do expect(project.path).to eq('path-project-Foo') end + it_behaves_like 'create project with default branch parameter' do + let(:request) { post api("/projects/user/#{user.id}", admin), params: params } + end + it 'responds with 400 on failure and not project' do expect { post api("/projects/user/#{user.id}", admin) } .not_to change { Project.count } @@ -1910,7 +1972,8 @@ RSpec.describe API::Projects do expect(json_response['id']).to eq(project.id) expect(json_response['description']).to eq(project.description) expect(json_response['default_branch']).to eq(project.default_branch) - expect(json_response['tag_list']).to be_an Array + expect(json_response['tag_list']).to be_an Array # deprecated in favor of 'topics' + expect(json_response['topics']).to be_an Array expect(json_response['archived']).to be_falsey expect(json_response['visibility']).to be_present expect(json_response['ssh_url_to_repo']).to be_present @@ -1987,7 +2050,8 @@ RSpec.describe API::Projects do expect(json_response['id']).to eq(project.id) expect(json_response['description']).to eq(project.description) expect(json_response['default_branch']).to eq(project.default_branch) - expect(json_response['tag_list']).to be_an Array + expect(json_response['tag_list']).to be_an Array # deprecated in favor of 'topics' + expect(json_response['topics']).to be_an Array expect(json_response['archived']).to be_falsey expect(json_response['visibility']).to be_present expect(json_response['ssh_url_to_repo']).to be_present @@ -2043,8 +2107,10 @@ RSpec.describe API::Projects do expect(json_response['ci_default_git_depth']).to eq(project.ci_default_git_depth) expect(json_response['ci_forward_deployment_enabled']).to eq(project.ci_forward_deployment_enabled) expect(json_response['merge_method']).to eq(project.merge_method.to_s) + expect(json_response['squash_option']).to eq(project.squash_option.to_s) expect(json_response['readme_url']).to eq(project.readme_url) expect(json_response).to have_key 'packages_enabled' + expect(json_response['keep_latest_artifact']).to be_present end it 'returns a group link with expiration date' do @@ -2755,7 +2821,7 @@ RSpec.describe API::Projects do expect(project.project_group_links).to be_empty end - it 'updates project authorization' do + it 'updates project authorization', :sidekiq_inline do expect do delete api("/projects/#{project.id}/share/#{group.id}", user) end.to( @@ -2902,6 +2968,18 @@ RSpec.describe API::Projects do end end + it 'updates default_branch' do + project_param = { default_branch: 'something_else' } + + put api("/projects/#{project.id}", user), params: project_param + + expect(response).to have_gitlab_http_status(:ok) + + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + it 'updates jobs_enabled' do project_param = { jobs_enabled: true } @@ -3027,6 +3105,26 @@ RSpec.describe API::Projects do expect(json_response['auto_devops_enabled']).to eq(false) end + + it 'updates topics using tag_list (deprecated)' do + project_param = { tag_list: 'topic1' } + + put api("/projects/#{project3.id}", user), params: project_param + + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response['topics']).to eq(%w[topic1]) + end + + it 'updates topics' do + project_param = { topics: 'topic2' } + + put api("/projects/#{project3.id}", user), params: project_param + + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response['topics']).to eq(%w[topic2]) + end end context 'when authenticated as project maintainer' do @@ -3203,6 +3301,24 @@ RSpec.describe API::Projects do expect { subject }.to change { project.reload.service_desk_enabled }.to(true) end end + + context 'when updating keep latest artifact' do + subject { put(api("/projects/#{project.id}", user), params: { keep_latest_artifact: true }) } + + before do + project.update!(keep_latest_artifact: false) + end + + it 'returns 200' do + subject + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'enables keep_latest_artifact' do + expect { subject }.to change { project.reload.keep_latest_artifact }.to(true) + end + end end describe 'POST /projects/:id/archive' do @@ -3882,6 +3998,48 @@ RSpec.describe API::Projects do end end + describe 'GET /projects/:id/storage' do + context 'when unauthenticated' do + it 'does not return project storage data' do + get api("/projects/#{project.id}/storage") + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + it 'returns project storage data when user is admin' do + get api("/projects/#{project.id}/storage", create(:admin)) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['project_id']).to eq(project.id) + expect(json_response['disk_path']).to eq(project.repository.disk_path) + expect(json_response['created_at']).to be_present + expect(json_response['repository_storage']).to eq(project.repository_storage) + end + + it 'does not return project storage data when user is not admin' do + get api("/projects/#{project.id}/storage", user3) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'responds with a 401 for unauthenticated users trying to access a non-existent project id' do + expect(Project.find_by(id: non_existing_record_id)).to be_nil + + get api("/projects/#{non_existing_record_id}/storage") + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'responds with a 403 for non-admin users trying to access a non-existent project id' do + expect(Project.find_by(id: non_existing_record_id)).to be_nil + + get api("/projects/#{non_existing_record_id}/storage", user3) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + it_behaves_like 'custom attributes endpoints', 'projects' do let(:attributable) { project } let(:other_attributable) { project2 } diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb index 718004a0087..86925e6a0ba 100644 --- a/spec/requests/api/pypi_packages_spec.rb +++ b/spec/requests/api/pypi_packages_spec.rb @@ -8,69 +8,59 @@ RSpec.describe API::PypiPackages do using RSpec::Parameterized::TableSyntax let_it_be(:user) { create(:user) } - let_it_be(:project, reload: true) { create(:project, :public) } + let_it_be_with_reload(:group) { create(:group) } + let_it_be_with_reload(:project) { create(:project, :public, group: group) } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } 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) } + let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } + let(:headers) { {} } - describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do + context 'simple API endpoint' do let_it_be(:package) { create(:pypi_package, project: project) } - let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" } - subject { get api(url) } + subject { get api(url), headers: headers } - context 'with valid project' do - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'PyPI package versions' | :success - 'PUBLIC' | :guest | true | true | 'PyPI package versions' | :success - 'PUBLIC' | :developer | true | false | 'PyPI package versions' | :success - 'PUBLIC' | :guest | true | false | 'PyPI package versions' | :success - 'PUBLIC' | :developer | false | true | 'PyPI package versions' | :success - 'PUBLIC' | :guest | false | true | 'PyPI package versions' | :success - 'PUBLIC' | :developer | false | false | 'PyPI package versions' | :success - 'PUBLIC' | :guest | false | false | 'PyPI package versions' | :success - 'PUBLIC' | :anonymous | false | true | 'PyPI package versions' | :success - 'PRIVATE' | :developer | true | true | 'PyPI package versions' | :success - 'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden - 'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found - 'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found - 'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized - end + describe 'GET /api/v4/groups/:id/-/packages/pypi/simple/:package_name' do + let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package.name}" } + let(:snowplow_gitlab_standard_context) { {} } - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + it_behaves_like 'pypi simple API endpoint' + it_behaves_like 'rejects PyPI access with unknown group id' - subject { get api(url), headers: headers } + context 'deploy tokens' do + let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: group) } before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) + group.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) end - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + it_behaves_like 'deploy token for package GET requests' end - end - context 'with a normalized package name' do - let_it_be(:package) { create(:pypi_package, project: project, name: 'my.package') } - let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" } - let(:headers) { basic_auth_header(user.username, personal_access_token.token) } + context 'job token' do + before do + project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) + group.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) + group.add_developer(user) + end - subject { get api(url), headers: headers } + it_behaves_like 'job token for package GET requests' + end - it_behaves_like 'PyPI package versions', :developer, :success + it_behaves_like 'a pypi user namespace endpoint' end - it_behaves_like 'deploy token for package GET requests' - - it_behaves_like 'job token for package GET requests' + describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do + let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } - it_behaves_like 'rejects PyPI access with unknown project id' + it_behaves_like 'pypi simple API endpoint' + it_behaves_like 'rejects PyPI access with unknown project id' + it_behaves_like 'deploy token for package GET requests' + it_behaves_like 'job token for package GET requests' + end end describe 'POST /api/v4/projects/:id/packages/pypi/authorize' do @@ -82,25 +72,25 @@ RSpec.describe API::PypiPackages do subject { post api(url), headers: headers } context 'with valid project' do - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process PyPI api request' | :success - 'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden - 'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized - 'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized - 'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden - 'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden - 'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized - 'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :developer | true | true | 'process PyPI api request' | :success - 'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden - 'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found - 'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found - 'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + :public | :developer | true | true | 'process PyPI api request' | :success + :public | :guest | true | true | 'process PyPI api request' | :forbidden + :public | :developer | true | false | 'process PyPI api request' | :unauthorized + :public | :guest | true | false | 'process PyPI api request' | :unauthorized + :public | :developer | false | true | 'process PyPI api request' | :forbidden + :public | :guest | false | true | 'process PyPI api request' | :forbidden + :public | :developer | false | false | 'process PyPI api request' | :unauthorized + :public | :guest | false | false | 'process PyPI api request' | :unauthorized + :public | :anonymous | false | true | 'process PyPI api request' | :unauthorized + :private | :developer | true | true | 'process PyPI api request' | :success + :private | :guest | true | true | 'process PyPI api request' | :forbidden + :private | :developer | true | false | 'process PyPI api request' | :unauthorized + :private | :guest | true | false | 'process PyPI api request' | :unauthorized + :private | :developer | false | true | 'process PyPI api request' | :not_found + :private | :guest | false | true | 'process PyPI api request' | :not_found + :private | :developer | false | false | 'process PyPI api request' | :unauthorized + :private | :guest | false | false | 'process PyPI api request' | :unauthorized + :private | :anonymous | false | true | 'process PyPI api request' | :unauthorized end with_them do @@ -109,7 +99,7 @@ RSpec.describe API::PypiPackages do let(:headers) { user_headers.merge(workhorse_headers) } before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) end it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] @@ -133,6 +123,7 @@ RSpec.describe API::PypiPackages do let(:base_params) { { requires_python: requires_python, version: '1.0.0', name: 'sample-project', sha256_digest: '123' } } let(:params) { base_params.merge(content: temp_file(file_name)) } let(:send_rewritten_field) { true } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } subject do workhorse_finalize( @@ -146,25 +137,25 @@ RSpec.describe API::PypiPackages do end context 'with valid project' do - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'PyPI package creation' | :created - 'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden - 'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized - 'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized - 'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden - 'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden - 'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized - 'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :developer | true | true | 'process PyPI api request' | :created - 'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden - 'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found - 'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found - 'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + :public | :developer | true | true | 'PyPI package creation' | :created + :public | :guest | true | true | 'process PyPI api request' | :forbidden + :public | :developer | true | false | 'process PyPI api request' | :unauthorized + :public | :guest | true | false | 'process PyPI api request' | :unauthorized + :public | :developer | false | true | 'process PyPI api request' | :forbidden + :public | :guest | false | true | 'process PyPI api request' | :forbidden + :public | :developer | false | false | 'process PyPI api request' | :unauthorized + :public | :guest | false | false | 'process PyPI api request' | :unauthorized + :public | :anonymous | false | true | 'process PyPI api request' | :unauthorized + :private | :developer | true | true | 'process PyPI api request' | :created + :private | :guest | true | true | 'process PyPI api request' | :forbidden + :private | :developer | true | false | 'process PyPI api request' | :unauthorized + :private | :guest | true | false | 'process PyPI api request' | :unauthorized + :private | :developer | false | true | 'process PyPI api request' | :not_found + :private | :guest | false | true | 'process PyPI api request' | :not_found + :private | :developer | false | false | 'process PyPI api request' | :unauthorized + :private | :guest | false | false | 'process PyPI api request' | :unauthorized + :private | :anonymous | false | true | 'process PyPI api request' | :unauthorized end with_them do @@ -173,7 +164,7 @@ RSpec.describe API::PypiPackages do let(:headers) { user_headers.merge(workhorse_headers) } before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) end it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] @@ -187,7 +178,7 @@ RSpec.describe API::PypiPackages do let(:headers) { user_headers.merge(workhorse_headers) } before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) end it_behaves_like 'process PyPI api request', :developer, :bad_request, true @@ -225,84 +216,27 @@ RSpec.describe API::PypiPackages do end end - describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do + context 'file download endpoint' 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(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" } - - subject { get api(url) } - - context 'with valid project' do - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'PyPI package download' | :success - 'PUBLIC' | :guest | true | true | 'PyPI package download' | :success - 'PUBLIC' | :developer | true | false | 'PyPI package download' | :success - 'PUBLIC' | :guest | true | false | 'PyPI package download' | :success - 'PUBLIC' | :developer | false | true | 'PyPI package download' | :success - 'PUBLIC' | :guest | false | true | 'PyPI package download' | :success - 'PUBLIC' | :developer | false | false | 'PyPI package download' | :success - 'PUBLIC' | :guest | false | false | 'PyPI package download' | :success - 'PUBLIC' | :anonymous | false | true | 'PyPI package download' | :success - 'PRIVATE' | :developer | true | true | 'PyPI package download' | :success - 'PRIVATE' | :guest | true | true | 'PyPI package download' | :success - 'PRIVATE' | :developer | true | false | 'PyPI package download' | :success - 'PRIVATE' | :guest | true | false | 'PyPI package download' | :success - 'PRIVATE' | :developer | false | true | 'PyPI package download' | :success - 'PRIVATE' | :guest | false | true | 'PyPI package download' | :success - 'PRIVATE' | :developer | false | false | 'PyPI package download' | :success - 'PRIVATE' | :guest | false | false | 'PyPI package download' | :success - 'PRIVATE' | :anonymous | false | true | 'PyPI package download' | :success - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - - subject { get api(url), headers: headers } + subject { get api(url), headers: headers } - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - end - - context 'with deploy token headers' do - let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) } - - context 'valid token' do - it_behaves_like 'returning response status', :success - end - - context 'invalid token' do - let(:headers) { basic_auth_header('foo', 'bar') } + describe 'GET /api/v4/groups/:id/-/packages/pypi/files/:sha256/*file_identifier' do + let(:url) { "/groups/#{group.id}/-/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" } + let(:snowplow_gitlab_standard_context) { {} } - it_behaves_like 'returning response status', :success - end + it_behaves_like 'pypi file download endpoint' + it_behaves_like 'rejects PyPI access with unknown group id' + it_behaves_like 'a pypi user namespace endpoint' end - context 'with job token headers' do - let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) } - - context 'valid token' do - it_behaves_like 'returning response status', :success - end - - context 'invalid token' do - let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, 'bar') } - - it_behaves_like 'returning response status', :success - end - - context 'invalid user' do - let(:headers) { basic_auth_header('foo', job.token) } + describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do + let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } - it_behaves_like 'returning response status', :success - end + it_behaves_like 'pypi file download endpoint' + it_behaves_like 'rejects PyPI access with unknown project id' end - - it_behaves_like 'rejects PyPI access with unknown project id' end end diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index dad3e34404b..81a4fcdbcac 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -775,7 +775,7 @@ RSpec.describe API::Releases do end context 'when using JOB-TOKEN auth' do - let(:job) { create(:ci_build, user: maintainer) } + let(:job) { create(:ci_build, user: maintainer, project: project) } let(:params) do { name: 'Another release', diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index a12b4dc9848..1b96efeca22 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -178,10 +178,12 @@ RSpec.describe API::Repositories do expect(headers['Content-Disposition']).to eq 'inline' end - it_behaves_like 'uncached response' do - before do - get api(route, current_user) - end + it 'defines an uncached header response' 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 context 'when sha does not exist' do diff --git a/spec/requests/api/rubygem_packages_spec.rb b/spec/requests/api/rubygem_packages_spec.rb index d6ad8186063..7d863b55bbe 100644 --- a/spec/requests/api/rubygem_packages_spec.rb +++ b/spec/requests/api/rubygem_packages_spec.rb @@ -10,10 +10,11 @@ RSpec.describe API::RubygemPackages do let_it_be_with_reload(:project) { create(:project) } 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) } + let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } 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(:headers) { {} } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } let(:tokens) do { @@ -162,6 +163,7 @@ RSpec.describe API::RubygemPackages 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 } } before do project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s)) @@ -304,6 +306,16 @@ RSpec.describe API::RubygemPackages do let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' } let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } } let(:headers) { user_headers.merge(workhorse_headers) } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: snowplow_user } } + let(:snowplow_user) do + if token_type == :deploy_token + deploy_token + elsif token_type == :job_token + job.user + else + user + end + end before do project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s)) diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 1f859622760..8701efcd65f 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -26,7 +26,7 @@ RSpec.describe API::Services do context 'project with services' do let!(:active_service) { create(:emails_on_push_service, project: project, active: true) } - let!(:service) { create(:custom_issue_tracker_service, project: project, active: false) } + let!(:service) { create(:custom_issue_tracker_integration, project: project, active: false) } it "returns a list of all active services" do get api("/projects/#{project.id}/services", user) diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 66c0dcaa36c..4a4aeaea714 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -113,6 +113,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do terms: 'Hello world!', performance_bar_allowed_group_path: group.full_path, diff_max_patch_bytes: 300_000, + diff_max_files: 2000, + diff_max_lines: 50000, default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE, local_markdown_version: 3, allow_local_requests_from_web_hooks_and_services: true, @@ -159,6 +161,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do expect(json_response['terms']).to eq('Hello world!') expect(json_response['performance_bar_allowed_group_id']).to eq(group.id) expect(json_response['diff_max_patch_bytes']).to eq(300_000) + expect(json_response['diff_max_files']).to eq(2000) + expect(json_response['diff_max_lines']).to eq(50000) expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_DEV_CAN_MERGE) expect(json_response['local_markdown_version']).to eq(3) expect(json_response['allow_local_requests_from_web_hooks_and_services']).to eq(true) diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 3c698cf577e..1aa1ad87be9 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -358,17 +358,6 @@ RSpec.describe API::Tags do expect(json_response['message']).to eq('Target foo is invalid') end - context 'lightweight tags with release notes' do - it 'creates a new tag' do - post api(route, current_user), params: { tag_name: tag_name, ref: 'master', release_description: 'Wow' } - - expect(response).to have_gitlab_http_status(:created) - expect(response).to match_response_schema('public_api/v4/tag') - expect(json_response['name']).to eq(tag_name) - expect(json_response['release']['description']).to eq('Wow') - end - end - context 'annotated tag' do it 'creates a new annotated tag' do # Identity must be set in .gitconfig to create annotated tag. @@ -440,122 +429,4 @@ RSpec.describe API::Tags do end end end - - describe 'POST /projects/:id/repository/tags/:tag_name/release' do - let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" } - let(:description) { 'Awesome release!' } - - shared_examples_for 'repository new release' do - it 'creates description for existing git tag' do - post api(route, user), params: { description: description } - - expect(response).to have_gitlab_http_status(:created) - expect(response).to match_response_schema('public_api/v4/release/tag_release') - expect(json_response['tag_name']).to eq(tag_name) - expect(json_response['description']).to eq(description) - end - - context 'when tag does not exist' do - let(:tag_name) { 'unknown' } - - it_behaves_like '404 response' do - let(:request) { post api(route, current_user), params: { description: description } } - let(:message) { '404 Tag Not Found' } - end - end - - context 'when repository is disabled' do - include_context 'disabled repository' - - it_behaves_like '403 response' do - let(:request) { post api(route, current_user), params: { description: description } } - end - end - end - - context 'when authenticated', 'as a maintainer' do - let(:current_user) { user } - - it_behaves_like 'repository new release' - - context 'requesting with the escaped project full path' do - let(:project_id) { CGI.escape(project.full_path) } - - it_behaves_like 'repository new release' - end - - context 'on tag with existing release' do - let!(:release) { create(:release, :legacy, project: project, tag: tag_name, description: description) } - - it 'returns 409 if there is already a release' do - post api(route, user), params: { description: description } - - expect(response).to have_gitlab_http_status(:conflict) - expect(json_response['message']).to eq('Release already exists') - end - end - end - end - - describe 'PUT id/repository/tags/:tag_name/release' do - let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" } - let(:description) { 'Awesome release!' } - let(:new_description) { 'The best release!' } - - shared_examples_for 'repository update release' do - context 'on tag with existing release' do - let!(:release) do - create(:release, - :legacy, - project: project, - tag: tag_name, - description: description) - end - - it 'updates the release description' do - put api(route, current_user), params: { description: new_description } - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['tag_name']).to eq(tag_name) - expect(json_response['description']).to eq(new_description) - end - end - - context 'when tag does not exist' do - let(:tag_name) { 'unknown' } - - it_behaves_like '403 response' do - let(:request) { put api(route, current_user), params: { description: new_description } } - let(:message) { '403 Forbidden' } - end - end - - context 'when repository is disabled' do - include_context 'disabled repository' - - it_behaves_like '403 response' do - let(:request) { put api(route, current_user), params: { description: new_description } } - end - end - end - - context 'when authenticated', 'as a maintainer' do - let(:current_user) { user } - - it_behaves_like 'repository update release' - - context 'requesting with the escaped project full path' do - let(:project_id) { CGI.escape(project.full_path) } - - it_behaves_like 'repository update release' - end - - context 'when release does not exist' do - it_behaves_like '403 response' do - let(:request) { put api(route, current_user), params: { description: new_description } } - let(:message) { '403 Forbidden' } - end - end - end - end end diff --git a/spec/requests/api/terraform/modules/v1/packages_spec.rb b/spec/requests/api/terraform/modules/v1/packages_spec.rb index d318b22cf27..b04f5ad9a94 100644 --- a/spec/requests/api/terraform/modules/v1/packages_spec.rb +++ b/spec/requests/api/terraform/modules/v1/packages_spec.rb @@ -12,7 +12,7 @@ RSpec.describe API::Terraform::Modules::V1::Packages do let_it_be(:package) { create(:terraform_module_package, project: project) } 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) } + let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } 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) } @@ -188,6 +188,7 @@ RSpec.describe API::Terraform::Modules::V1::Packages do with_them do let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' } let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/#{package.version}/file?token=#{token}") } + let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace } } before do group.update!(visibility: visibility.to_s) @@ -330,6 +331,16 @@ RSpec.describe API::Terraform::Modules::V1::Packages do let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' } let(:user_headers) { user_role == :anonymous ? {} : { token_header => token } } let(:headers) { user_headers.merge(workhorse_headers) } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: snowplow_user } } + let(:snowplow_user) do + if token_type == :deploy_token + deploy_token + elsif token_type == :job_token + job.user + else + user + end + end before do project.update!(visibility: visibility.to_s) diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb index 2cb3c8e9ab5..5d2635126e8 100644 --- a/spec/requests/api/terraform/state_spec.rb +++ b/spec/requests/api/terraform/state_spec.rb @@ -25,10 +25,6 @@ RSpec.describe API::Terraform::State do context 'without authentication' do let(:auth_header) { basic_auth_header('bad', 'token') } - before do - stub_feature_flags(usage_data_p_terraform_state_api_unique_users: false) - end - it 'does not track unique event' do expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event) @@ -156,15 +152,6 @@ RSpec.describe API::Terraform::State do expect(response).to have_gitlab_http_status(:ok) expect(Gitlab::Json.parse(response.body)).to be_empty end - - context 'on Unicorn', :unicorn do - it 'updates the state' do - expect { request }.to change { Terraform::State.count }.by(0) - - expect(response).to have_gitlab_http_status(:ok) - expect(Gitlab::Json.parse(response.body)).to be_empty - end - end end context 'without body' do @@ -200,15 +187,6 @@ RSpec.describe API::Terraform::State do expect(response).to have_gitlab_http_status(:ok) expect(Gitlab::Json.parse(response.body)).to be_empty end - - context 'on Unicorn', :unicorn do - it 'creates a new state' do - expect { request }.to change { Terraform::State.count }.by(1) - - expect(response).to have_gitlab_http_status(:ok) - expect(Gitlab::Json.parse(response.body)).to be_empty - end - end end context 'without body' do diff --git a/spec/requests/api/unleash_spec.rb b/spec/requests/api/unleash_spec.rb index 0b70d62b093..9989f8d28bd 100644 --- a/spec/requests/api/unleash_spec.rb +++ b/spec/requests/api/unleash_spec.rb @@ -176,34 +176,9 @@ RSpec.describe API::Unleash do it_behaves_like 'authenticated request' context 'with version 1 (legacy) feature flags' do - let(:feature_flag) { create(:operations_feature_flag, project: project, name: 'feature1', active: true, version: 1) } + let(:feature_flag) { create(:operations_feature_flag, :legacy_flag, project: project, name: 'feature1', active: true, version: 1) } - it_behaves_like 'support multiple environments' - - context 'with a list of feature flags' do - let(:headers) { { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "production" } } - let!(:enabled_feature_flag) { create(:operations_feature_flag, project: project, name: 'feature1', active: true, version: 1) } - let!(:disabled_feature_flag) { create(:operations_feature_flag, project: project, name: 'feature2', active: false, version: 1) } - - it 'responds with a list of features' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['version']).to eq(1) - expect(json_response['features']).not_to be_empty - expect(json_response['features'].map { |f| f['name'] }.sort).to eq(%w[feature1 feature2]) - expect(json_response['features'].sort_by {|f| f['name'] }.map { |f| f['enabled'] }).to eq([true, false]) - end - - it 'matches json schema' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('unleash/unleash') - end - end - - it 'returns a feature flag strategy' do + it 'does not return a legacy feature flag' do create(:operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: 'sandbox', @@ -215,81 +190,7 @@ RSpec.describe API::Unleash do get api(features_url), headers: headers expect(response).to have_gitlab_http_status(:ok) - expect(json_response['features'].first['enabled']).to eq(true) - strategies = json_response['features'].first['strategies'] - expect(strategies).to eq([{ - "name" => "gradualRolloutUserId", - "parameters" => { - "percentage" => "50", - "groupId" => "default" - } - }]) - end - - it 'returns a default strategy for a scope' do - create(:operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: 'sandbox', active: true) - headers = { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "sandbox" } - - get api(features_url), headers: headers - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['features'].first['enabled']).to eq(true) - strategies = json_response['features'].first['strategies'] - expect(strategies).to eq([{ "name" => "default", "parameters" => {} }]) - end - - it 'returns multiple strategies for a feature flag' do - create(:operations_feature_flag_scope, - feature_flag: feature_flag, - environment_scope: 'staging', - active: true, - strategies: [{ name: "userWithId", parameters: { userIds: "max,fred" } }, - { name: "gradualRolloutUserId", - parameters: { groupId: "default", percentage: "50" } }]) - headers = { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "staging" } - - get api(features_url), headers: headers - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['features'].first['enabled']).to eq(true) - strategies = json_response['features'].first['strategies'].sort_by { |s| s['name'] } - expect(strategies).to eq([{ - "name" => "gradualRolloutUserId", - "parameters" => { - "percentage" => "50", - "groupId" => "default" - } - }, { - "name" => "userWithId", - "parameters" => { - "userIds" => "max,fred" - } - }]) - end - - it 'returns a disabled feature when the flag is disabled' do - flag = create(:operations_feature_flag, project: project, name: 'test_feature', active: false, version: 1) - create(:operations_feature_flag_scope, feature_flag: flag, environment_scope: 'production', active: true) - headers = { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "production" } - - get api(features_url), headers: headers - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['features'].first['enabled']).to eq(false) - end - - context "with an inactive scope" do - let!(:scope) { create(:operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: 'production', active: false, strategies: [{ name: "default", parameters: {} }]) } - let(:headers) { { "UNLEASH-INSTANCEID" => client.token, "UNLEASH-APPNAME" => "production" } } - - it 'returns a disabled feature' do - get api(features_url), headers: headers - - expect(response).to have_gitlab_http_status(:ok) - feature_json = json_response['features'].first - expect(feature_json['enabled']).to eq(false) - expect(feature_json['strategies']).to eq([{ 'name' => 'default', 'parameters' => {} }]) - end + expect(json_response['features']).to be_empty end end @@ -534,63 +435,6 @@ RSpec.describe API::Unleash do }]) end end - - context 'when mixing version 1 and version 2 feature flags' do - it 'returns both types of flags when both match' do - feature_flag_a = create(:operations_feature_flag, project: project, - name: 'feature_a', active: true, version: 2) - strategy = create(:operations_strategy, feature_flag: feature_flag_a, - name: 'userWithId', parameters: { userIds: 'user8' }) - create(:operations_scope, strategy: strategy, environment_scope: 'staging') - feature_flag_b = create(:operations_feature_flag, project: project, - name: 'feature_b', active: true, version: 1) - create(:operations_feature_flag_scope, feature_flag: feature_flag_b, - active: true, strategies: [{ name: 'default', parameters: {} }], environment_scope: 'staging') - - get api(features_url), headers: { 'UNLEASH-INSTANCEID' => client.token, 'UNLEASH-APPNAME' => 'staging' } - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['features'].sort_by {|f| f['name']}).to eq([{ - 'name' => 'feature_a', - 'enabled' => true, - 'strategies' => [{ - 'name' => 'userWithId', - 'parameters' => { 'userIds' => 'user8' } - }] - }, { - 'name' => 'feature_b', - 'enabled' => true, - 'strategies' => [{ - 'name' => 'default', - 'parameters' => {} - }] - }]) - end - - it 'returns legacy flags when only legacy flags match' do - feature_flag_a = create(:operations_feature_flag, project: project, - name: 'feature_a', active: true, version: 2) - strategy = create(:operations_strategy, feature_flag: feature_flag_a, - name: 'userWithId', parameters: { userIds: 'user8' }) - create(:operations_scope, strategy: strategy, environment_scope: 'production') - feature_flag_b = create(:operations_feature_flag, project: project, - name: 'feature_b', active: true, version: 1) - create(:operations_feature_flag_scope, feature_flag: feature_flag_b, - active: true, strategies: [{ name: 'default', parameters: {} }], environment_scope: 'staging') - - get api(features_url), headers: { 'UNLEASH-INSTANCEID' => client.token, 'UNLEASH-APPNAME' => 'staging' } - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['features']).to eq([{ - 'name' => 'feature_b', - 'enabled' => true, - 'strategies' => [{ - 'name' => 'default', - 'parameters' => {} - }] - }]) - end - end end end diff --git a/spec/requests/api/users_preferences_spec.rb b/spec/requests/api/users_preferences_spec.rb index db03786ed2a..97e37263ee6 100644 --- a/spec/requests/api/users_preferences_spec.rb +++ b/spec/requests/api/users_preferences_spec.rb @@ -8,11 +8,19 @@ RSpec.describe API::Users do describe 'PUT /user/preferences/' do context "with correct attributes and a logged in user" do it 'returns a success status and the value has been changed' do - put api("/user/preferences", user), params: { view_diffs_file_by_file: true } + put api("/user/preferences", user), params: { + view_diffs_file_by_file: true, + show_whitespace_in_diffs: true + } expect(response).to have_gitlab_http_status(:ok) expect(json_response['view_diffs_file_by_file']).to eq(true) - expect(user.reload.view_diffs_file_by_file).to be_truthy + expect(json_response['show_whitespace_in_diffs']).to eq(true) + + user.reload + + expect(user.view_diffs_file_by_file).to be_truthy + expect(user.show_whitespace_in_diffs).to be_truthy end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 71fdd986f20..a9231b65c8f 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -2077,6 +2077,29 @@ RSpec.describe API::Users do it_behaves_like 'get user info', 'v4' end + describe "GET /user/preferences" do + context "when unauthenticated" do + it "returns authentication error" do + get api("/user/preferences") + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + context "when authenticated" do + it "returns user preferences" do + user.user_preference.view_diffs_file_by_file = false + user.user_preference.show_whitespace_in_diffs = true + user.save! + + get api("/user/preferences", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response["view_diffs_file_by_file"]).to eq(user.user_preference.view_diffs_file_by_file) + expect(json_response["show_whitespace_in_diffs"]).to eq(user.user_preference.show_whitespace_in_diffs) + end + end + end + describe "GET /user/keys" do context "when unauthenticated" do it "returns authentication error" do diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb index d35aab40ca9..64fde3db19f 100644 --- a/spec/requests/api/wikis_spec.rb +++ b/spec/requests/api/wikis_spec.rb @@ -16,8 +16,19 @@ RSpec.describe API::Wikis do include WorkhorseHelpers include AfterNextHelpers - let(:user) { create(:user) } - let(:group) { create(:group).tap { |g| g.add_owner(user) } } + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group).tap { |g| g.add_owner(user) } } + let_it_be(:group_project) { create(:project, :wiki_repo, namespace: group) } + + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:project_wiki_disabled) do + create(:project, :wiki_repo, :wiki_disabled).tap do |project| + project.add_developer(developer) + project.add_maintainer(maintainer) + end + end + let(:project_wiki) { create(:project_wiki, project: project, user: user) } let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } } let(:expected_keys_with_content) { %w(content format slug title) } @@ -32,7 +43,7 @@ RSpec.describe API::Wikis do let(:url) { "/projects/#{project.id}/wikis" } context 'when wiki is disabled' do - let(:project) { create(:project, :wiki_repo, :wiki_disabled) } + let(:project) { project_wiki_disabled } context 'when user is guest' do before do @@ -44,9 +55,7 @@ RSpec.describe API::Wikis do context 'when user is developer' do before do - project.add_developer(user) - - get api(url, user) + get api(url, developer) end include_examples 'wiki API 403 Forbidden' @@ -54,9 +63,7 @@ RSpec.describe API::Wikis do context 'when user is maintainer' do before do - project.add_maintainer(user) - - get api(url, user) + get api(url, maintainer) end include_examples 'wiki API 403 Forbidden' @@ -125,7 +132,7 @@ RSpec.describe API::Wikis do let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" } context 'when wiki is disabled' do - let(:project) { create(:project, :wiki_repo, :wiki_disabled) } + let(:project) { project_wiki_disabled } context 'when user is guest' do before do @@ -137,9 +144,7 @@ RSpec.describe API::Wikis do context 'when user is developer' do before do - project.add_developer(user) - - get api(url, user) + get api(url, developer) end include_examples 'wiki API 403 Forbidden' @@ -147,9 +152,7 @@ RSpec.describe API::Wikis do context 'when user is maintainer' do before do - project.add_maintainer(user) - - get api(url, user) + get api(url, maintainer) end include_examples 'wiki API 403 Forbidden' @@ -249,7 +252,7 @@ RSpec.describe API::Wikis do let(:url) { "/projects/#{project.id}/wikis" } context 'when wiki is disabled' do - let(:project) { create(:project, :wiki_disabled, :wiki_repo) } + let(:project) { project_wiki_disabled } context 'when user is guest' do before do @@ -261,8 +264,7 @@ RSpec.describe API::Wikis do context 'when user is developer' do before do - project.add_developer(user) - post(api(url, user), params: payload) + post(api(url, developer), params: payload) end include_examples 'wiki API 403 Forbidden' @@ -270,8 +272,7 @@ RSpec.describe API::Wikis do context 'when user is maintainer' do before do - project.add_maintainer(user) - post(api(url, user), params: payload) + post(api(url, maintainer), params: payload) end include_examples 'wiki API 403 Forbidden' @@ -469,7 +470,7 @@ RSpec.describe API::Wikis do end context 'when wiki belongs to a group project' do - let(:project) { create(:project, :wiki_repo, namespace: group) } + let(:project) { group_project } include_examples 'wikis API updates wiki page' end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 279c65fc2f4..7cf46f6adc6 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -883,10 +883,10 @@ RSpec.describe 'Git HTTP requests' do context 'when admin mode is enabled', :enable_admin_mode do it_behaves_like 'can download code only' - it 'downloads from other project get status 403' do + it 'downloads from other project get status 404' do clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:not_found) end end diff --git a/spec/requests/groups/email_campaigns_controller_spec.rb b/spec/requests/groups/email_campaigns_controller_spec.rb index 48297ec4cb6..4d630ef6710 100644 --- a/spec/requests/groups/email_campaigns_controller_spec.rb +++ b/spec/requests/groups/email_campaigns_controller_spec.rb @@ -9,10 +9,11 @@ RSpec.describe Groups::EmailCampaignsController do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } let_it_be(:user) { create(:user) } + let(:track) { 'create' } let(:series) { '0' } let(:schema) { described_class::EMAIL_CAMPAIGNS_SCHEMA_URL } - let(:subject_line_text) { Gitlab::Email::Message::InProductMarketing.for(track.to_sym).new(group: group, series: series.to_i).subject_line } + let(:subject_line_text) { Gitlab::Email::Message::InProductMarketing.for(track.to_sym).new(group: group, user: user, series: series.to_i).subject_line } let(:data) do { namespace_id: group.id, @@ -51,7 +52,9 @@ RSpec.describe Groups::EmailCampaignsController do context: [{ schema: described_class::EMAIL_CAMPAIGNS_SCHEMA_URL, data: { namespace_id: group.id, series: series.to_i, subject_line: subject_line_text, track: track.to_s } - }] + }], + user: user, + namespace: group ) end @@ -91,7 +94,7 @@ RSpec.describe Groups::EmailCampaignsController do describe 'track parameter' do context 'when valid' do - where(track: Namespaces::InProductMarketingEmailsService::TRACKS.keys) + where(track: Namespaces::InProductMarketingEmailsService::TRACKS.keys.without(:experience)) with_them do it_behaves_like 'track and redirect' @@ -109,7 +112,7 @@ RSpec.describe Groups::EmailCampaignsController do describe 'series parameter' do context 'when valid' do - where(series: (0..Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.length - 1).to_a) + where(series: (0..Namespaces::InProductMarketingEmailsService::TRACKS[:create][:interval_days].length - 1).to_a) with_them do it_behaves_like 'track and redirect' @@ -117,7 +120,7 @@ RSpec.describe Groups::EmailCampaignsController do end context 'when invalid' do - where(series: [-1, nil, Namespaces::InProductMarketingEmailsService::INTERVAL_DAYS.length]) + where(series: [-1, nil, Namespaces::InProductMarketingEmailsService::TRACKS[:create][:interval_days].length]) with_them do it_behaves_like 'no track and 404' diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 0e3a0252638..fda8b2ecec6 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -569,7 +569,7 @@ RSpec.describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } # I'm not sure what this tests that is different from the previous test - it_behaves_like 'LFS http 403 response' + it_behaves_like 'LFS http 404 response' end end @@ -1043,7 +1043,7 @@ RSpec.describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } # I'm not sure what this tests that is different from the previous test - it_behaves_like 'LFS http 403 response' + it_behaves_like 'LFS http 404 response' end end diff --git a/spec/requests/oauth/tokens_controller_spec.rb b/spec/requests/oauth/tokens_controller_spec.rb index c3cdae2cd21..1967d0ba8b1 100644 --- a/spec/requests/oauth/tokens_controller_spec.rb +++ b/spec/requests/oauth/tokens_controller_spec.rb @@ -3,12 +3,71 @@ require 'spec_helper' RSpec.describe Oauth::TokensController do - it 'allows cross-origin POST requests' do - post '/oauth/token', headers: { 'Origin' => 'http://notgitlab.com' } + let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } } + let(:other_headers) { {} } + let(:headers) { cors_request_headers.merge(other_headers)} - expect(response.headers['Access-Control-Allow-Origin']).to eq '*' - expect(response.headers['Access-Control-Allow-Methods']).to eq 'POST' - expect(response.headers['Access-Control-Allow-Headers']).to be_nil - expect(response.headers['Access-Control-Allow-Credentials']).to be_nil + shared_examples 'cross-origin POST request' do + it 'allows cross-origin requests' do + expect(response.headers['Access-Control-Allow-Origin']).to eq '*' + expect(response.headers['Access-Control-Allow-Methods']).to eq 'POST' + expect(response.headers['Access-Control-Allow-Headers']).to be_nil + expect(response.headers['Access-Control-Allow-Credentials']).to be_nil + end + end + + shared_examples 'CORS preflight OPTIONS request' do + it 'returns 200' do + expect(response).to have_gitlab_http_status(:ok) + end + + it 'allows cross-origin requests' do + expect(response.headers['Access-Control-Allow-Origin']).to eq '*' + expect(response.headers['Access-Control-Allow-Methods']).to eq 'POST' + expect(response.headers['Access-Control-Allow-Headers']).to eq 'Authorization' + expect(response.headers['Access-Control-Allow-Credentials']).to be_nil + end + end + + describe 'POST /oauth/token' do + before do + post '/oauth/token', headers: headers + end + + it_behaves_like 'cross-origin POST request' + end + + describe 'OPTIONS /oauth/token' do + let(:other_headers) { { 'Access-Control-Request-Headers' => 'Authorization', 'Access-Control-Request-Method' => 'POST' } } + + before do + options '/oauth/token', headers: headers + end + + it_behaves_like 'CORS preflight OPTIONS request' + end + + describe 'POST /oauth/revoke' do + let(:other_headers) { { 'Content-Type' => 'application/x-www-form-urlencoded' } } + + before do + post '/oauth/revoke', headers: headers, params: { token: '12345' } + end + + it 'returns 200' do + expect(response).to have_gitlab_http_status(:ok) + end + + it_behaves_like 'cross-origin POST request' + end + + describe 'OPTIONS /oauth/revoke' do + let(:other_headers) { { 'Access-Control-Request-Headers' => 'Authorization', 'Access-Control-Request-Method' => 'POST' } } + + before do + options '/oauth/revoke', headers: headers + end + + it_behaves_like 'CORS preflight OPTIONS request' end end diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index 7b682d76150..5bf786f2290 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -41,6 +41,8 @@ RSpec.describe 'OpenID Connect requests' do } end + let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } } + def request_access_token! login_as user @@ -81,6 +83,24 @@ RSpec.describe 'OpenID Connect requests' do end end + shared_examples 'cross-origin GET request' do + it 'allows cross-origin request' do + expect(response.headers['Access-Control-Allow-Origin']).to eq '*' + expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, HEAD' + expect(response.headers['Access-Control-Allow-Headers']).to be_nil + expect(response.headers['Access-Control-Allow-Credentials']).to be_nil + end + end + + shared_examples 'cross-origin GET and POST request' do + it 'allows cross-origin request' do + expect(response.headers['Access-Control-Allow-Origin']).to eq '*' + expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, HEAD, POST' + expect(response.headers['Access-Control-Allow-Headers']).to be_nil + expect(response.headers['Access-Control-Allow-Credentials']).to be_nil + end + end + context 'Application with OpenID scope' do let(:application) { create :oauth_application, scopes: 'openid' } @@ -180,6 +200,51 @@ RSpec.describe 'OpenID Connect requests' do expect(response).to redirect_to('/users/sign_in') end end + + context 'OpenID Discovery keys' do + context 'with a cross-origin request' do + before do + get '/oauth/discovery/keys', headers: cors_request_headers + end + + it 'returns data' do + expect(response).to have_gitlab_http_status(:ok) + end + + it_behaves_like 'cross-origin GET request' + end + + context 'with a cross-origin preflight OPTIONS request' do + before do + options '/oauth/discovery/keys', headers: cors_request_headers + end + + it_behaves_like 'cross-origin GET request' + end + end + + context 'OpenID WebFinger endpoint' do + context 'with a cross-origin request' do + before do + get '/.well-known/webfinger', headers: cors_request_headers, params: { resource: 'user@example.com' } + end + + it 'returns data' do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['subject']).to eq('user@example.com') + end + + it_behaves_like 'cross-origin GET request' + end + end + + context 'with a cross-origin preflight OPTIONS request' do + before do + options '/.well-known/webfinger', headers: cors_request_headers, params: { resource: 'user@example.com' } + end + + it_behaves_like 'cross-origin GET request' + end end context 'OpenID configuration information' do @@ -191,6 +256,27 @@ RSpec.describe 'OpenID Connect requests' do expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys') expect(json_response['scopes_supported']).to eq(%w[api read_user read_api read_repository write_repository sudo openid profile email]) end + + context 'with a cross-origin request' do + before do + get '/.well-known/openid-configuration', headers: cors_request_headers + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['issuer']).to eq('http://localhost') + expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys') + expect(json_response['scopes_supported']).to eq(%w[api read_user read_api read_repository write_repository sudo openid profile email]) + end + + it_behaves_like 'cross-origin GET request' + end + + context 'with a cross-origin preflight OPTIONS request' do + before do + options '/.well-known/openid-configuration', headers: cors_request_headers + end + + it_behaves_like 'cross-origin GET request' + end end context 'Application with OpenID and email scopes' do @@ -218,6 +304,30 @@ RSpec.describe 'OpenID Connect requests' do it 'has true in email_verified claim' do expect(json_response['email_verified']).to eq(true) end + + context 'with a cross-origin request' do + before do + get '/oauth/userinfo', headers: cors_request_headers + end + + it_behaves_like 'cross-origin GET and POST request' + end + + context 'with a cross-origin POST request' do + before do + post '/oauth/userinfo', headers: cors_request_headers + end + + it_behaves_like 'cross-origin GET and POST request' + end + + context 'with a cross-origin preflight OPTIONS request' do + before do + options '/oauth/userinfo', headers: cors_request_headers + end + + it_behaves_like 'cross-origin GET and POST request' + end end context 'ID token payload' do diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index f092cbf26a4..5a38f92221f 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -675,48 +675,6 @@ RSpec.describe UsersController do end end - describe 'GET #suggests' do - context 'when user exists' do - it 'returns JSON indicating the user exists and a suggestion' do - get user_suggests_url user.username - - expected_json = { exists: true, suggests: ["#{user.username}1"] }.to_json - expect(response.body).to eq(expected_json) - end - - context 'when the casing is different' do - let(:user) { create(:user, username: 'CamelCaseUser') } - - it 'returns JSON indicating the user exists and a suggestion' do - get user_suggests_url user.username.downcase - - expected_json = { exists: true, suggests: ["#{user.username.downcase}1"] }.to_json - expect(response.body).to eq(expected_json) - end - end - end - - context 'when the user does not exist' do - it 'returns JSON indicating the user does not exist' do - get user_suggests_url 'foo' - - expected_json = { exists: false, suggests: [] }.to_json - expect(response.body).to eq(expected_json) - end - - context 'when a user changed their username' do - let(:redirect_route) { user.namespace.redirect_routes.create!(path: 'old-username') } - - it 'returns JSON indicating a user by that username does not exist' do - get user_suggests_url 'old-username' - - expected_json = { exists: false, suggests: [] }.to_json - expect(response.body).to eq(expected_json) - end - end - end - end - describe '#ensure_canonical_path' do before do sign_in(user) |