Commit 3007979c authored by David Fernandez's avatar David Fernandez Committed by Etienne Baqué

Refactor NPM requests specs for shared endpoints

Use tabled based specs to better test different conditions.
Make sure that we use the Bearer http authorization header and not the
request parameter.
Move package tags shared examples in a dedicated file for npm specs.
parent 0ebd272f
......@@ -6,7 +6,7 @@ RSpec.describe API::NpmInstancePackages do
include_context 'npm api setup'
describe 'GET /api/v4/packages/npm/*package_name' do
it_behaves_like 'handling get metadata requests' do
it_behaves_like 'handling get metadata requests', scope: :instance do
let(:url) { api("/packages/npm/#{package_name}") }
end
end
......
......@@ -6,25 +6,25 @@ RSpec.describe API::NpmProjectPackages do
include_context 'npm api setup'
describe 'GET /api/v4/projects/:id/packages/npm/*package_name' do
it_behaves_like 'handling get metadata requests' do
it_behaves_like 'handling get metadata requests', scope: :project do
let(:url) { api("/projects/#{project.id}/packages/npm/#{package_name}") }
end
end
describe 'GET /api/v4/projects/:id/packages/npm/-/package/*package_name/dist-tags' do
it_behaves_like 'handling get dist tags requests' do
it_behaves_like 'handling get dist tags requests', scope: :project do
let(:url) { api("/projects/#{project.id}/packages/npm/-/package/#{package_name}/dist-tags") }
end
end
describe 'PUT /api/v4/projects/:id/packages/npm/-/package/*package_name/dist-tags/:tag' do
it_behaves_like 'handling create dist tag requests' do
it_behaves_like 'handling create dist tag requests', scope: :project do
let(:url) { api("/projects/#{project.id}/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
end
end
describe 'DELETE /api/v4/projects/:id/packages/npm/-/package/*package_name/dist-tags/:tag' do
it_behaves_like 'handling delete dist tag requests' do
it_behaves_like 'handling delete dist tag requests', scope: :project do
let(:url) { api("/projects/#{project.id}/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
end
end
......@@ -32,10 +32,14 @@ RSpec.describe API::NpmProjectPackages do
describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do
let_it_be(:package_file) { package.package_files.first }
let(:params) { {} }
let(:url) { api("/projects/#{project.id}/packages/npm/#{package_file.package.name}/-/#{package_file.file_name}") }
let(:headers) { {} }
let(:url) { api("/projects/#{project.id}/packages/npm/#{package.name}/-/#{package_file.file_name}") }
subject { get(url, params: params) }
subject { get(url, headers: headers) }
before do
project.add_developer(user)
end
shared_examples 'a package file that requires auth' do
it 'denies download with no token' do
......@@ -45,7 +49,7 @@ RSpec.describe API::NpmProjectPackages do
end
context 'with access token' do
let(:params) { { access_token: token.token } }
let(:headers) { build_token_auth_header(token.token) }
it 'returns the file' do
subject
......@@ -56,7 +60,7 @@ RSpec.describe API::NpmProjectPackages do
end
context 'with job token' do
let(:params) { { job_token: job.token } }
let(:headers) { build_token_auth_header(job.token) }
it 'returns the file' do
subject
......@@ -86,7 +90,7 @@ RSpec.describe API::NpmProjectPackages do
it_behaves_like 'a package file that requires auth'
context 'with guest' do
let(:params) { { access_token: token.token } }
let(:headers) { build_token_auth_header(token.token) }
it 'denies download when not enough permissions' do
project.add_guest(user)
......@@ -108,7 +112,11 @@ RSpec.describe API::NpmProjectPackages do
end
describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do
RSpec.shared_examples 'handling invalid record with 400 error' do
before do
project.add_developer(user)
end
shared_examples 'handling invalid record with 400 error' do
it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do
expect { upload_package_with_token(package_name, params) }
.not_to change { project.packages.count }
......@@ -261,7 +269,9 @@ RSpec.describe API::NpmProjectPackages do
end
def upload_package(package_name, params = {})
put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params: params
token = params.delete(:access_token) || params.delete(:job_token)
headers = build_token_auth_header(token)
put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params: params, headers: headers
end
def upload_package_with_token(package_name, params = {})
......
......@@ -4,10 +4,10 @@ RSpec.shared_context 'npm api setup' do
include PackagesManagerApiSpecHelpers
include HttpBasicAuthHelpers
let_it_be(:user) { create(:user) }
let_it_be(:user, reload: true) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
let_it_be(:package, reload: true) { create(:npm_package, project: project) }
let_it_be(:package, reload: true) { create(:npm_package, project: project, name: "@#{group.path}/scoped_package") }
let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
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) }
......@@ -15,8 +15,15 @@ RSpec.shared_context 'npm api setup' do
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
let(:package_name) { package.name }
end
before do
project.add_developer(user)
RSpec.shared_context 'set package name from package name type' do
let(:package_name) do
case package_name_type
when :scoped_naming_convention
"@#{group.path}/scoped-package"
when :non_existing
'non-existing-package'
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'handling get metadata requests' do
RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
using RSpec::Parameterized::TableSyntax
let_it_be(:package_dependency_link1) { create(:packages_dependency_link, package: package, dependency_type: :dependencies) }
let_it_be(:package_dependency_link2) { create(:packages_dependency_link, package: package, dependency_type: :devDependencies) }
let_it_be(:package_dependency_link3) { create(:packages_dependency_link, package: package, dependency_type: :bundleDependencies) }
let_it_be(:package_dependency_link4) { create(:packages_dependency_link, package: package, dependency_type: :peerDependencies) }
let(:params) { {} }
let(:headers) { {} }
subject { get(url, params: params, headers: headers) }
subject { get(url, headers: headers) }
shared_examples 'returning the npm package info' do
it 'returns the package info' do
shared_examples 'accept metadata request' do |status:|
it 'accepts the metadata request' do
subject
expect_a_valid_package_response
expect(response).to have_gitlab_http_status(status)
expect(response.media_type).to eq('application/json')
expect(response).to match_response_schema('public_api/v4/packages/npm_package')
expect(json_response['name']).to eq(package.name)
expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version')
::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any
end
expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags')
end
shared_examples 'a package that requires auth' do
it 'denies request without oauth token' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
context 'with oauth token' do
let(:params) { { access_token: token.token } }
it 'returns the package info with oauth token' do
shared_examples 'reject metadata request' do |status:|
it 'rejects the metadata request' do
subject
expect_a_valid_package_response
expect(response).to have_gitlab_http_status(status)
end
end
context 'with job token' do
let(:params) { { job_token: job.token } }
it 'returns the package info with running job token' do
shared_examples 'redirect metadata request' do |status:|
it 'redirects metadata request' do
subject
expect_a_valid_package_response
expect(response).to have_gitlab_http_status(:found)
expect(response.headers['Location']).to eq("https://registry.npmjs.org/#{package_name}")
end
end
where(:auth, :package_name_type, :request_forward, :visibility, :user_role, :expected_result, :expected_status) do
nil | :scoped_naming_convention | true | 'PUBLIC' | nil | :accept | :ok
nil | :scoped_naming_convention | false | 'PUBLIC' | nil | :accept | :ok
nil | :non_existing | true | 'PUBLIC' | nil | :redirect | :redirected
nil | :non_existing | false | 'PUBLIC' | nil | :reject | :not_found
nil | :scoped_naming_convention | true | 'PRIVATE' | nil | :reject | :not_found
nil | :scoped_naming_convention | false | 'PRIVATE' | nil | :reject | :not_found
nil | :non_existing | true | 'PRIVATE' | nil | :redirect | :redirected
nil | :non_existing | false | 'PRIVATE' | nil | :reject | :not_found
nil | :scoped_naming_convention | true | 'INTERNAL' | nil | :reject | :not_found
nil | :scoped_naming_convention | false | 'INTERNAL' | nil | :reject | :not_found
nil | :non_existing | true | 'INTERNAL' | nil | :redirect | :redirected
nil | :non_existing | false | 'INTERNAL' | nil | :reject | :not_found
:oauth | :scoped_naming_convention | true | 'PUBLIC' | :guest | :accept | :ok
:oauth | :scoped_naming_convention | true | 'PUBLIC' | :reporter | :accept | :ok
:oauth | :scoped_naming_convention | false | 'PUBLIC' | :guest | :accept | :ok
:oauth | :scoped_naming_convention | false | 'PUBLIC' | :reporter | :accept | :ok
:oauth | :non_existing | true | 'PUBLIC' | :guest | :redirect | :redirected
:oauth | :non_existing | true | 'PUBLIC' | :reporter | :redirect | :redirected
:oauth | :non_existing | false | 'PUBLIC' | :guest | :reject | :not_found
:oauth | :non_existing | false | 'PUBLIC' | :reporter | :reject | :not_found
:oauth | :scoped_naming_convention | true | 'PRIVATE' | :guest | :reject | :forbidden
:oauth | :scoped_naming_convention | true | 'PRIVATE' | :reporter | :accept | :ok
:oauth | :scoped_naming_convention | false | 'PRIVATE' | :guest | :reject | :forbidden
:oauth | :scoped_naming_convention | false | 'PRIVATE' | :reporter | :accept | :ok
:oauth | :non_existing | true | 'PRIVATE' | :guest | :redirect | :redirected
:oauth | :non_existing | true | 'PRIVATE' | :reporter | :redirect | :redirected
:oauth | :non_existing | false | 'PRIVATE' | :guest | :reject | :forbidden
:oauth | :non_existing | false | 'PRIVATE' | :reporter | :reject | :not_found
:oauth | :scoped_naming_convention | true | 'INTERNAL' | :guest | :accept | :ok
:oauth | :scoped_naming_convention | true | 'INTERNAL' | :reporter | :accept | :ok
:oauth | :scoped_naming_convention | false | 'INTERNAL' | :guest | :accept | :ok
:oauth | :scoped_naming_convention | false | 'INTERNAL' | :reporter | :accept | :ok
:oauth | :non_existing | true | 'INTERNAL' | :guest | :redirect | :redirected
:oauth | :non_existing | true | 'INTERNAL' | :reporter | :redirect | :redirected
:oauth | :non_existing | false | 'INTERNAL' | :guest | :reject | :not_found
:oauth | :non_existing | false | 'INTERNAL' | :reporter | :reject | :not_found
:personal_access_token | :scoped_naming_convention | true | 'PUBLIC' | :guest | :accept | :ok
:personal_access_token | :scoped_naming_convention | true | 'PUBLIC' | :reporter | :accept | :ok
:personal_access_token | :scoped_naming_convention | false | 'PUBLIC' | :guest | :accept | :ok
:personal_access_token | :scoped_naming_convention | false | 'PUBLIC' | :reporter | :accept | :ok
:personal_access_token | :non_existing | true | 'PUBLIC' | :guest | :redirect | :redirected
:personal_access_token | :non_existing | true | 'PUBLIC' | :reporter | :redirect | :redirected
:personal_access_token | :non_existing | false | 'PUBLIC' | :guest | :reject | :not_found
:personal_access_token | :non_existing | false | 'PUBLIC' | :reporter | :reject | :not_found
:personal_access_token | :scoped_naming_convention | true | 'PRIVATE' | :guest | :reject | :forbidden
:personal_access_token | :scoped_naming_convention | true | 'PRIVATE' | :reporter | :accept | :ok
:personal_access_token | :scoped_naming_convention | false | 'PRIVATE' | :guest | :reject | :forbidden
:personal_access_token | :scoped_naming_convention | false | 'PRIVATE' | :reporter | :accept | :ok
:personal_access_token | :non_existing | true | 'PRIVATE' | :guest | :redirect | :redirected
:personal_access_token | :non_existing | true | 'PRIVATE' | :reporter | :redirect | :redirected
:personal_access_token | :non_existing | false | 'PRIVATE' | :guest | :reject | :forbidden
:personal_access_token | :non_existing | false | 'PRIVATE' | :reporter | :reject | :not_found
:personal_access_token | :scoped_naming_convention | true | 'INTERNAL' | :guest | :accept | :ok
:personal_access_token | :scoped_naming_convention | true | 'INTERNAL' | :reporter | :accept | :ok
:personal_access_token | :scoped_naming_convention | false | 'INTERNAL' | :guest | :accept | :ok
:personal_access_token | :scoped_naming_convention | false | 'INTERNAL' | :reporter | :accept | :ok
:personal_access_token | :non_existing | true | 'INTERNAL' | :guest | :redirect | :redirected
:personal_access_token | :non_existing | true | 'INTERNAL' | :reporter | :redirect | :redirected
:personal_access_token | :non_existing | false | 'INTERNAL' | :guest | :reject | :not_found
:personal_access_token | :non_existing | false | 'INTERNAL' | :reporter | :reject | :not_found
:job_token | :scoped_naming_convention | true | 'PUBLIC' | :developer | :accept | :ok
:job_token | :scoped_naming_convention | false | 'PUBLIC' | :developer | :accept | :ok
:job_token | :non_existing | true | 'PUBLIC' | :developer | :redirect | :redirected
:job_token | :non_existing | false | 'PUBLIC' | :developer | :reject | :not_found
:job_token | :scoped_naming_convention | true | 'PRIVATE' | :developer | :accept | :ok
:job_token | :scoped_naming_convention | false | 'PRIVATE' | :developer | :accept | :ok
:job_token | :non_existing | true | 'PRIVATE' | :developer | :redirect | :redirected
:job_token | :non_existing | false | 'PRIVATE' | :developer | :reject | :not_found
:job_token | :scoped_naming_convention | true | 'INTERNAL' | :developer | :accept | :ok
:job_token | :scoped_naming_convention | false | 'INTERNAL' | :developer | :accept | :ok
:job_token | :non_existing | true | 'INTERNAL' | :developer | :redirect | :redirected
:job_token | :non_existing | false | 'INTERNAL' | :developer | :reject | :not_found
:deploy_token | :scoped_naming_convention | true | 'PUBLIC' | nil | :accept | :ok
:deploy_token | :scoped_naming_convention | false | 'PUBLIC' | nil | :accept | :ok
:deploy_token | :non_existing | true | 'PUBLIC' | nil | :redirect | :redirected
:deploy_token | :non_existing | false | 'PUBLIC' | nil | :reject | :not_found
:deploy_token | :scoped_naming_convention | true | 'PRIVATE' | nil | :accept | :ok
:deploy_token | :scoped_naming_convention | false | 'PRIVATE' | nil | :accept | :ok
:deploy_token | :non_existing | true | 'PRIVATE' | nil | :redirect | :redirected
:deploy_token | :non_existing | false | 'PRIVATE' | nil | :reject | :not_found
:deploy_token | :scoped_naming_convention | true | 'INTERNAL' | nil | :accept | :ok
:deploy_token | :scoped_naming_convention | false | 'INTERNAL' | nil | :accept | :ok
:deploy_token | :non_existing | true | 'INTERNAL' | nil | :redirect | :redirected
:deploy_token | :non_existing | false | 'INTERNAL' | nil | :reject | :not_found
end
with_them do
include_context 'set package name from package name type'
let(:headers) do
case auth
when :oauth
build_token_auth_header(token.token)
when :personal_access_token
build_token_auth_header(personal_access_token.token)
when :job_token
build_token_auth_header(job.token)
when :deploy_token
build_token_auth_header(deploy_token.token)
else
{}
end
end
it 'denies request without running job token' do
job.update!(status: :success)
before do
project.send("add_#{user_role}", user) if user_role
project.update!(visibility: Gitlab::VisibilityLevel.const_get(visibility, false))
package.update!(name: package_name) unless package_name == 'non-existing-package'
stub_application_setting(npm_package_requests_forwarding: request_forward)
end
subject
example_name = "#{params[:expected_result]} metadata request"
status = params[:expected_status]
expect(response).to have_gitlab_http_status(:unauthorized)
if scope == :instance && params[:package_name_type] != :scoped_naming_convention
if params[:request_forward]
example_name = 'redirect metadata request'
status = :redirected
else
example_name = 'reject metadata request'
status = :not_found
end
end
context 'with deploy token' do
let(:headers) { build_token_auth_header(deploy_token.token) }
it_behaves_like example_name, status: status
end
it 'returns the package info with deploy token' do
subject
context 'with a developer' do
let(:headers) { build_token_auth_header(personal_access_token.token) }
expect_a_valid_package_response
end
end
before do
project.add_developer(user)
end
context 'a public project' do
it_behaves_like 'returning the npm package info'
context 'project path with a dot' do
before do
project.update!(path: 'foo.bar')
end
it_behaves_like 'returning the npm package info'
it_behaves_like 'accept metadata request', status: :ok
end
context 'with request forward disabled' do
context 'with a job token' do
let(:headers) { build_token_auth_header(job.token) }
before do
stub_application_setting(npm_package_requests_forwarding: false)
job.update!(status: :success)
end
it_behaves_like 'returning the npm package info'
context 'with unknown package' do
let(:package_name) { 'unknown' }
it 'returns the proper response' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
it_behaves_like 'reject metadata request', status: :unauthorized
end
end
end
context 'with request forward enabled' do
before do
stub_application_setting(npm_package_requests_forwarding: true)
end
RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
using RSpec::Parameterized::TableSyntax
include_context 'set package name from package name type'
it_behaves_like 'returning the npm package info'
let_it_be(:package_tag1) { create(:packages_tag, package: package) }
let_it_be(:package_tag2) { create(:packages_tag, package: package) }
context 'with unknown package' do
let(:package_name) { 'unknown' }
let(:headers) { {} }
it 'returns a redirect' do
subject
subject { get(url, headers: headers) }
expect(response).to have_gitlab_http_status(:found)
expect(response.headers['Location']).to eq('https://registry.npmjs.org/unknown')
shared_examples 'reject package tags request' do |status:|
before do
package.update!(name: package_name) unless package_name == 'non-existing-package'
end
it_behaves_like 'a gitlab tracking event', described_class.name, 'npm_request_forward'
end
end
it_behaves_like 'returning response status', status
end
context 'internal project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
shared_examples 'handling different package names, visibilities and user roles' do
where(:package_name_type, :visibility, :user_role, :expected_result, :expected_status) do
:scoped_naming_convention | 'PUBLIC' | :anonymous | :accept | :ok
:scoped_naming_convention | 'PUBLIC' | :guest | :accept | :ok
:scoped_naming_convention | 'PUBLIC' | :reporter | :accept | :ok
:non_existing | 'PUBLIC' | :anonymous | :reject | :not_found
:non_existing | 'PUBLIC' | :guest | :reject | :not_found
:non_existing | 'PUBLIC' | :reporter | :reject | :not_found
it_behaves_like 'a package that requires auth'
end
:scoped_naming_convention | 'PRIVATE' | :anonymous | :reject | :not_found
:scoped_naming_convention | 'PRIVATE' | :guest | :reject | :forbidden
:scoped_naming_convention | 'PRIVATE' | :reporter | :accept | :ok
:non_existing | 'PRIVATE' | :anonymous | :reject | :not_found
:non_existing | 'PRIVATE' | :guest | :reject | :forbidden
:non_existing | 'PRIVATE' | :reporter | :reject | :not_found
context 'private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
:scoped_naming_convention | 'INTERNAL' | :anonymous | :reject | :not_found
:scoped_naming_convention | 'INTERNAL' | :guest | :accept | :ok
:scoped_naming_convention | 'INTERNAL' | :reporter | :accept | :ok
:non_existing | 'INTERNAL' | :anonymous | :reject | :not_found
:non_existing | 'INTERNAL' | :guest | :reject | :not_found
:non_existing | 'INTERNAL' | :reporter | :reject | :not_found
end
it_behaves_like 'a package that requires auth'
with_them do
let(:anonymous) { user_role == :anonymous }
context 'with guest' do
let(:params) { { access_token: token.token } }
subject { get(url, headers: anonymous ? {} : headers) }
it 'denies request when not enough permissions' do
project.add_guest(user)
before do
project.send("add_#{user_role}", user) unless anonymous
project.update!(visibility: Gitlab::VisibilityLevel.const_get(visibility, false))
end
subject
example_name = "#{params[:expected_result]} package tags request"
status = params[:expected_status]
expect(response).to have_gitlab_http_status(:forbidden)
if scope == :instance && params[:package_name_type] != :scoped_naming_convention
example_name = 'reject package tags request'
status = :not_found
end
it_behaves_like example_name, status: status
end
end
def expect_a_valid_package_response
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/json')
expect(response).to match_response_schema('public_api/v4/packages/npm_package')
expect(json_response['name']).to eq(package.name)
expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version')
::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any
context 'with oauth token' do
let(:headers) { build_token_auth_header(token.token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags')
context 'with personal access token' do
let(:headers) { build_token_auth_header(personal_access_token.token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
end
RSpec.shared_examples 'handling get dist tags requests' do
let_it_be(:package_tag1) { create(:packages_tag, package: package) }
let_it_be(:package_tag2) { create(:packages_tag, package: package) }
let(:params) { {} }
RSpec.shared_examples 'handling create dist tag requests' do |scope: :project|
using RSpec::Parameterized::TableSyntax
include_context 'set package name from package name type'
subject { get(url, params: params) }
let_it_be(:tag_name) { 'test' }
context 'with public project' do
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
let(:params) { {} }
let(:version) { package.version }
let(:env) { { 'api.request.body': version } }
let(:headers) { {} }
it_behaves_like 'returns package tags', :maintainer
it_behaves_like 'returns package tags', :developer
it_behaves_like 'returns package tags', :reporter
it_behaves_like 'returns package tags', :guest
shared_examples 'reject create package tag request' do |status:|
before do
package.update!(name: package_name) unless package_name == 'non-existing-package'
end
context 'with unauthenticated user' do
it_behaves_like 'returns package tags', :no_type
it_behaves_like 'returning response status', status
end
shared_examples 'handling different package names, visibilities and user roles' do
where(:package_name_type, :visibility, :user_role, :expected_result, :expected_status) do
:scoped_naming_convention | 'PUBLIC' | :anonymous | :reject | :forbidden
:scoped_naming_convention | 'PUBLIC' | :guest | :reject | :forbidden
:scoped_naming_convention | 'PUBLIC' | :developer | :accept | :ok
:non_existing | 'PUBLIC' | :anonymous | :reject | :forbidden
:non_existing | 'PUBLIC' | :guest | :reject | :forbidden
:non_existing | 'PUBLIC' | :developer | :reject | :not_found
:scoped_naming_convention | 'PRIVATE' | :anonymous | :reject | :not_found
:scoped_naming_convention | 'PRIVATE' | :guest | :reject | :forbidden
:scoped_naming_convention | 'PRIVATE' | :developer | :accept | :ok
:non_existing | 'PRIVATE' | :anonymous | :reject | :not_found
:non_existing | 'PRIVATE' | :guest | :reject | :forbidden
:non_existing | 'PRIVATE' | :developer | :reject | :not_found
:scoped_naming_convention | 'INTERNAL' | :anonymous | :reject | :forbidden
:scoped_naming_convention | 'INTERNAL' | :guest | :reject | :forbidden
:scoped_naming_convention | 'INTERNAL' | :developer | :accept | :ok
:non_existing | 'INTERNAL' | :anonymous | :reject | :forbidden
:non_existing | 'INTERNAL' | :guest | :reject | :forbidden
:non_existing | 'INTERNAL' | :developer | :reject | :not_found
end
context 'with private project' do
with_them do
let(:anonymous) { user_role == :anonymous }
subject { put(url, env: env, headers: headers) }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
project.send("add_#{user_role}", user) unless anonymous
project.update!(visibility: Gitlab::VisibilityLevel.const_get(visibility, false))
end
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
example_name = "#{params[:expected_result]} create package tag request"
status = params[:expected_status]
it_behaves_like 'returns package tags', :maintainer
it_behaves_like 'returns package tags', :developer
it_behaves_like 'returns package tags', :reporter
it_behaves_like 'rejects package tags access', :guest, :forbidden
if scope == :instance && params[:package_name_type] != :scoped_naming_convention
example_name = 'reject create package tag request'
status = :not_found
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :not_found
it_behaves_like example_name, status: status
end
end
end
RSpec.shared_examples 'handling create dist tag requests' do
let_it_be(:tag_name) { 'test' }
let(:params) { {} }
let(:env) { {} }
let(:version) { package.version }
subject { put(url, env: env, params: params) }
context 'with public project' do
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
let(:env) { { 'api.request.body': version } }
context 'with oauth token' do
let(:headers) { build_token_auth_header(token.token) }
it_behaves_like 'create package tag', :maintainer
it_behaves_like 'create package tag', :developer
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
it_behaves_like 'handling different package names, visibilities and user roles'
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
end
context 'with personal access token' do
let(:headers) { build_token_auth_header(personal_access_token.token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
end
RSpec.shared_examples 'handling delete dist tag requests' do
RSpec.shared_examples 'handling delete dist tag requests' do |scope: :project|
using RSpec::Parameterized::TableSyntax
include_context 'set package name from package name type'
let_it_be(:package_tag) { create(:packages_tag, package: package) }
let(:params) { {} }
let(:tag_name) { package_tag.name }
let(:headers) { {} }
subject { delete(url, params: params) }
context 'with public project' do
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
it_behaves_like 'delete package tag', :maintainer
it_behaves_like 'rejects package tags access', :developer, :forbidden
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
shared_examples 'reject delete package tag request' do |status:|
before do
package.update!(name: package_name) unless package_name == 'non-existing-package'
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
it_behaves_like 'returning response status', status
end
shared_examples 'handling different package names, visibilities and user roles' do
where(:package_name_type, :visibility, :user_role, :expected_result, :expected_status) do
:scoped_naming_convention | 'PUBLIC' | :anonymous | :reject | :forbidden
:scoped_naming_convention | 'PUBLIC' | :guest | :reject | :forbidden
:scoped_naming_convention | 'PUBLIC' | :maintainer | :accept | :ok
:non_existing | 'PUBLIC' | :anonymous | :reject | :forbidden
:non_existing | 'PUBLIC' | :guest | :reject | :forbidden
:non_existing | 'PUBLIC' | :maintainer | :reject | :not_found
:scoped_naming_convention | 'PRIVATE' | :anonymous | :reject | :not_found
:scoped_naming_convention | 'PRIVATE' | :guest | :reject | :forbidden
:scoped_naming_convention | 'PRIVATE' | :maintainer | :accept | :ok
:non_existing | 'INTERNAL' | :anonymous | :reject | :forbidden
:non_existing | 'INTERNAL' | :guest | :reject | :forbidden
:non_existing | 'INTERNAL' | :maintainer | :reject | :not_found
end
context 'with private project' do
with_them do
let(:anonymous) { user_role == :anonymous }
subject { delete(url, headers: headers) }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
project.send("add_#{user_role}", user) unless anonymous
project.update!(visibility: Gitlab::VisibilityLevel.const_get(visibility, false))
end
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
example_name = "#{params[:expected_result]} delete package tag request"
status = params[:expected_status]
it_behaves_like 'delete package tag', :maintainer
it_behaves_like 'rejects package tags access', :developer, :forbidden
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
if scope == :instance && params[:package_name_type] != :scoped_naming_convention
example_name = 'reject delete package tag request'
status = :not_found
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
it_behaves_like example_name, status: status
end
end
context 'with oauth token' do
let(:headers) { build_token_auth_header(token.token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
context 'with personal access token' do
let(:headers) { build_token_auth_header(personal_access_token.token) }
it_behaves_like 'handling different package names, visibilities and user roles'
end
end
# frozen_string_literal: true
RSpec.shared_examples 'rejects package tags access' do |user_type, status|
context "for user type #{user_type}" do
RSpec.shared_examples 'rejects package tags access' do |status:|
before do
project.send("add_#{user_type}", user) unless user_type == :no_type
package.update!(name: package_name) unless package_name == 'non-existing-package'
end
it_behaves_like 'returning response status', status
end
end
RSpec.shared_examples 'returns package tags' do |user_type|
RSpec.shared_examples 'accept package tags request' do |status:|
using RSpec::Parameterized::TableSyntax
before do
stub_application_setting(npm_package_requests_forwarding: false)
project.send("add_#{user_type}", user) unless user_type == :no_type
end
it_behaves_like 'returning response status', :success
context 'with valid package name' do
before do
package.update!(name: package_name) unless package_name == 'non-existing-package'
end
it_behaves_like 'returning response status', status
it 'returns a valid json response' do
subject
......@@ -36,6 +38,7 @@ RSpec.shared_examples 'returns package tags' do |user_type|
expect(json_response[package_tag2.name]).to eq(package.version)
expect(json_response['latest']).to eq(package.version)
end
end
context 'with invalid package name' do
where(:package_name, :status) do
......@@ -49,11 +52,12 @@ RSpec.shared_examples 'returns package tags' do |user_type|
end
end
RSpec.shared_examples 'create package tag' do |user_type|
RSpec.shared_examples 'accept create package tag request' do |user_type|
using RSpec::Parameterized::TableSyntax
context 'with valid package name' do
before do
project.send("add_#{user_type}", user) unless user_type == :no_type
package.update!(name: package_name) unless package_name == 'non-existing-package'
end
it_behaves_like 'returning response status', :no_content
......@@ -92,6 +96,7 @@ RSpec.shared_examples 'create package tag' do |user_type|
expect(response.body).to be_empty
end
end
end
context 'with invalid package name' do
where(:package_name, :status) do
......@@ -129,14 +134,14 @@ RSpec.shared_examples 'create package tag' do |user_type|
end
end
RSpec.shared_examples 'delete package tag' do |user_type|
RSpec.shared_examples 'accept delete package tag request' do |user_type|
using RSpec::Parameterized::TableSyntax
context 'with valid package name' do
before do
project.send("add_#{user_type}", user) unless user_type == :no_type
package.update!(name: package_name) unless package_name == 'non-existing-package'
end
context "for #{user_type} user" do
it_behaves_like 'returning response status', :no_content
it 'returns a valid response' do
......@@ -157,6 +162,7 @@ RSpec.shared_examples 'delete package tag' do |user_type|
it_behaves_like 'returning response status', :not_found
end
end
context 'with invalid package name' do
where(:package_name, :status) do
......@@ -181,5 +187,4 @@ RSpec.shared_examples 'delete package tag' do |user_type|
it_behaves_like 'returning response status', params[:status]
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment