Commit 052b5a88 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'ac-releases-api-with-assets-update-delete-links' into 'master'

Support CURD operation for individual asset links in Release API

See merge request gitlab-org/gitlab-ce!24100
parents b83be503 6bd7e1b8
...@@ -141,6 +141,7 @@ module API ...@@ -141,6 +141,7 @@ module API
mount ::API::ProtectedBranches mount ::API::ProtectedBranches
mount ::API::ProtectedTags mount ::API::ProtectedTags
mount ::API::Releases mount ::API::Releases
mount ::API::Release::Links
mount ::API::Repositories mount ::API::Repositories
mount ::API::Runner mount ::API::Runner
mount ::API::Runners mount ::API::Runners
......
# frozen_string_literal: true
module API
module Release
class Links < Grape::API
include PaginationParams
RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
before { error!('404 Not Found', 404) unless Feature.enabled?(:releases_page, user_project) }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
params do
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
end
resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
resource :assets do
desc 'Get a list of links of a release' do
detail 'This feature was introduced in GitLab 11.7.'
success Entities::Releases::Link
end
params do
use :pagination
end
get 'links' do
authorize! :read_release, release
present paginate(release.links.sorted), with: Entities::Releases::Link
end
desc 'Create a link of a release' do
detail 'This feature was introduced in GitLab 11.7.'
success Entities::Releases::Link
end
params do
requires :name, type: String, desc: 'The name of the link'
requires :url, type: String, desc: 'The URL of the link'
end
post 'links' do
authorize! :create_release, release
new_link = release.links.create(declared_params(include_missing: false))
if new_link.persisted?
present new_link, with: Entities::Releases::Link
else
render_api_error!(new_link.errors.messages, 400)
end
end
params do
requires :link_id, type: String, desc: 'The id of the link'
end
resource 'links/:link_id' do
desc 'Get a link detail of a release' do
detail 'This feature was introduced in GitLab 11.7.'
success Entities::Releases::Link
end
get do
authorize! :read_release, release
present link, with: Entities::Releases::Link
end
desc 'Update a link of a release' do
detail 'This feature was introduced in GitLab 11.7.'
success Entities::Releases::Link
end
params do
optional :name, type: String, desc: 'The name of the link'
optional :url, type: String, desc: 'The URL of the link'
at_least_one_of :name, :url
end
put do
authorize! :update_release, release
if link.update(declared_params(include_missing: false))
present link, with: Entities::Releases::Link
else
render_api_error!(link.errors.messages, 400)
end
end
desc 'Delete a link of a release' do
detail 'This feature was introduced in GitLab 11.7.'
success Entities::Releases::Link
end
delete do
authorize! :destroy_release, release
if link.destroy
present link, with: Entities::Releases::Link
else
render_api_error!(link.errors.messages, 400)
end
end
end
end
end
end
helpers do
def release
@release ||= user_project.releases.find_by_tag!(params[:tag])
end
def link
@link ||= release.links.find(params[:link_id])
end
end
end
end
end
...@@ -15,15 +15,7 @@ ...@@ -15,15 +15,7 @@
}, },
"assets": { "assets": {
"count": { "type": "integer" }, "count": { "type": "integer" },
"links": { "links": { "$ref": "release/links.json" },
"type": "array",
"items": {
"id": "integer",
"name": "string",
"url": "string",
"external": "boolean"
}
},
"sources": { "sources": {
"type": "array", "type": "array",
"items": { "items": {
......
{
"type": "object",
"required": ["name", "url"],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"url": { "type": "string" },
"external": { "type": "boolean" }
},
"additionalProperties": false
}
{
"type": "array",
"items": { "$ref": "link.json" }
}
# frozen_string_literal: true
require 'spec_helper'
describe API::Release::Links do
let(:project) { create(:project, :repository, :private) }
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
let(:non_project_member) { create(:user) }
let(:commit) { create(:commit, project: project) }
let!(:release) do
create(:release,
project: project,
tag: 'v0.1',
author: maintainer)
end
before do
project.add_maintainer(maintainer)
project.add_reporter(reporter)
project.repository.add_tag(maintainer, 'v0.1', commit.id)
end
describe 'GET /projects/:id/releases/:tag_name/assets/links' do
context 'when there are two release links' do
let!(:release_link_1) { create(:release_link, release: release, created_at: 2.days.ago) }
let!(:release_link_2) { create(:release_link, release: release, created_at: 1.day.ago) }
it 'returns 200 HTTP status' do
get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer)
expect(response).to have_gitlab_http_status(:ok)
end
it 'returns release links ordered by created_at' do
get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer)
expect(json_response.count).to eq(2)
expect(json_response.first['name']).to eq(release_link_2.name)
expect(json_response.second['name']).to eq(release_link_1.name)
end
it 'matches response schema' do
get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer)
expect(response).to match_response_schema('release/links')
end
end
context 'when release does not exist' do
let!(:release) { }
it_behaves_like '404 response' do
let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer) }
let(:message) { '404 Not found' }
end
end
context 'when user is not a project member' do
it_behaves_like '404 response' do
let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member) }
let(:message) { '404 Project Not Found' }
end
context 'when project is public' do
let(:project) { create(:project, :repository, :public) }
it 'allows the request' do
get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member)
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(releases_page: false)
end
it_behaves_like '404 response' do
let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer) }
end
end
end
describe 'GET /projects/:id/releases/:tag_name/assets/links/:link_id' do
let!(:release_link) { create(:release_link, release: release) }
it 'returns 200 HTTP status' do
get api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
expect(response).to have_gitlab_http_status(:ok)
end
it 'returns a link entry' do
get api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
expect(json_response['name']).to eq(release_link.name)
expect(json_response['url']).to eq(release_link.url)
end
it 'matches response schema' do
get api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
expect(response).to match_response_schema('release/link')
end
context 'when specified tag is not found in the project' do
it_behaves_like '404 response' do
let(:request) { get api("/projects/#{project.id}/releases/non_existing_tag/assets/links/#{release_link.id}", maintainer) }
end
end
context 'when user is not a project member' do
it_behaves_like '404 response' do
let(:request) { get api("/projects/#{project.id}/releases/non_existing_tag/assets/links/#{release_link.id}", non_project_member) }
end
context 'when project is public' do
let(:project) { create(:project, :repository, :public) }
it 'allows the request' do
get api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member)
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(releases_page: false)
end
it_behaves_like '404 response' do
let(:request) { get api("/projects/#{project.id}/releases/non_existing_tag/assets/links/#{release_link.id}", maintainer) }
end
end
end
describe 'POST /projects/:id/releases/:tag_name/assets/links' do
let(:params) do
{
name: 'awesome-app.dmg',
url: 'https://example.com/download/awesome-app.dmg'
}
end
it 'accepts the request' do
post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params
expect(response).to have_gitlab_http_status(:created)
end
it 'creates a new release' do
expect do
post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params
end.to change { Releases::Link.count }.by(1)
release.reload
expect(release.links.last.name).to eq('awesome-app.dmg')
expect(release.links.last.url).to eq('https://example.com/download/awesome-app.dmg')
end
it 'matches response schema' do
post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params
expect(response).to match_response_schema('release/link')
end
context 'when name is empty' do
let(:params) do
{
name: '',
url: 'https://example.com/download/awesome-app.dmg'
}
end
it_behaves_like '400 response' do
let(:request) do
post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer),
params: params
end
end
end
context 'when user is a reporter' do
it_behaves_like '403 response' do
let(:request) do
post api("/projects/#{project.id}/releases/v0.1/assets/links", reporter),
params: params
end
end
end
context 'when user is not a project member' do
it 'forbids the request' do
post api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member),
params: params
expect(response).to have_gitlab_http_status(:not_found)
end
context 'when project is public' do
let(:project) { create(:project, :repository, :public) }
it 'forbids the request' do
post api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member),
params: params
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'when the same link already exists' do
before do
create(:release_link,
release: release,
name: 'awesome-app.dmg',
url: 'https://example.com/download/awesome-app.dmg')
end
it_behaves_like '400 response' do
let(:request) do
post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer),
params: params
end
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(releases_page: false)
end
it_behaves_like '404 response' do
let(:request) do
post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer),
params: params
end
end
end
end
describe 'PUT /projects/:id/releases/:tag_name/assets/links/:link_id' do
let(:params) { { name: 'awesome-app.msi' } }
let!(:release_link) { create(:release_link, release: release) }
it 'accepts the request' do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
params: params
expect(response).to have_gitlab_http_status(:ok)
end
it 'updates the name' do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
params: params
expect(json_response['name']).to eq('awesome-app.msi')
end
it 'does not update the url' do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
params: params
expect(json_response['url']).to eq(release_link.url)
end
it 'matches response schema' do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
params: params
expect(response).to match_response_schema('release/link')
end
context 'when params is empty' do
let(:params) { {} }
it 'does not allow the request' do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
params: params
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'when there are no corresponding release link' do
let!(:release_link) { }
it_behaves_like '404 response' do
let(:request) do
put api("/projects/#{project.id}/releases/v0.1/assets/links/1", maintainer),
params: params
end
end
end
context 'when user is a reporter' do
it_behaves_like '403 response' do
let(:request) do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", reporter),
params: params
end
end
end
context 'when user is not a project member' do
it_behaves_like '404 response' do
let(:request) do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member),
params: params
end
end
context 'when project is public' do
let(:project) { create(:project, :repository, :public) }
it_behaves_like '403 response' do
let(:request) do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member),
params: params
end
end
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(releases_page: false)
end
it_behaves_like '404 response' do
let(:request) do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
params: params
end
end
end
end
describe 'DELETE /projects/:id/releases/:tag_name/assets/links/:link_id' do
let!(:release_link) do
create(:release_link, release: release)
end
it 'accepts the request' do
delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
expect(response).to have_gitlab_http_status(:ok)
end
it 'destroys the release link' do
expect do
delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
end.to change { Releases::Link.count }.by(-1)
end
it 'matches response schema' do
delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
expect(response).to match_response_schema('release/link')
end
context 'when there are no corresponding release link' do
let!(:release_link) { }
it_behaves_like '404 response' do
let(:request) do
delete api("/projects/#{project.id}/releases/v0.1/assets/links/1", maintainer)
end
end
end
context 'when user is a reporter' do
it_behaves_like '403 response' do
let(:request) do
delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", reporter)
end
end
end
context 'when user is not a project member' do
it_behaves_like '404 response' do
let(:request) do
delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member)
end
end
context 'when project is public' do
let(:project) { create(:project, :repository, :public) }
it_behaves_like '403 response' do
let(:request) do
delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member)
end
end
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(releases_page: false)
end
it_behaves_like '404 response' do
let(:request) do
delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
end
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