[GITEA] Add Upload URL to release API

- Resolves https://codeberg.org/forgejo/forgejo/issues/580
- Return a `upload_field` to any release API response, which points to
the API URL for uploading new assets.
- Adds unit test.
- Adds integration testing to verify URL is returned correctly and that
upload endpoint actually works

(cherry picked from commit 074413a2dc)
(cherry picked from commit 33feed4723)
(cherry picked from commit 1ca21b95ff)
(cherry picked from commit 874f07cec2)
(cherry picked from commit a78c538d8e)
This commit is contained in:
Gusted 2023-06-24 15:11:39 +02:00 committed by Earl Warren
parent 9697e48c1f
commit bef38ce382
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
6 changed files with 79 additions and 0 deletions

View file

@ -133,6 +133,11 @@ func (r *Release) HTMLURL() string {
return r.Repo.HTMLURL() + "/releases/tag/" + util.PathEscapeSegments(r.TagName) return r.Repo.HTMLURL() + "/releases/tag/" + util.PathEscapeSegments(r.TagName)
} }
// APIUploadURL the api url to upload assets to a release. release must have attributes loaded
func (r *Release) APIUploadURL() string {
return r.APIURL() + "/assets"
}
// Link the relative url for a release on the web UI. release must have attributes loaded // Link the relative url for a release on the web UI. release must have attributes loaded
func (r *Release) Link() string { func (r *Release) Link() string {
return r.Repo.Link() + "/releases/tag/" + util.PathEscapeSegments(r.TagName) return r.Repo.Link() + "/releases/tag/" + util.PathEscapeSegments(r.TagName)

View file

@ -18,6 +18,7 @@ type Release struct {
HTMLURL string `json:"html_url"` HTMLURL string `json:"html_url"`
TarURL string `json:"tarball_url"` TarURL string `json:"tarball_url"`
ZipURL string `json:"zipball_url"` ZipURL string `json:"zipball_url"`
UploadURL string `json:"upload_url"`
IsDraft bool `json:"draft"` IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"` IsPrerelease bool `json:"prerelease"`
// swagger:strfmt date-time // swagger:strfmt date-time

View file

@ -22,6 +22,7 @@ func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_mode
HTMLURL: r.HTMLURL(), HTMLURL: r.HTMLURL(),
TarURL: r.TarURL(), TarURL: r.TarURL(),
ZipURL: r.ZipURL(), ZipURL: r.ZipURL(),
UploadURL: r.APIUploadURL(),
IsDraft: r.IsDraft, IsDraft: r.IsDraft,
IsPrerelease: r.IsPrerelease, IsPrerelease: r.IsPrerelease,
CreatedAt: r.CreatedUnix.AsTime(), CreatedAt: r.CreatedUnix.AsTime(),

View file

@ -0,0 +1,28 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package convert
import (
"testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
func TestRelease_ToRelease(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
release1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: 1})
release1.LoadAttributes(db.DefaultContext)
apiRelease := ToAPIRelease(db.DefaultContext, repo1, release1)
assert.NotNil(t, apiRelease)
assert.EqualValues(t, 1, apiRelease.ID)
assert.EqualValues(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1", apiRelease.URL)
assert.EqualValues(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1/assets", apiRelease.UploadURL)
}

View file

@ -20886,6 +20886,10 @@
"type": "string", "type": "string",
"x-go-name": "Target" "x-go-name": "Target"
}, },
"upload_url": {
"type": "string",
"x-go-name": "UploadURL"
},
"url": { "url": {
"type": "string", "type": "string",
"x-go-name": "URL" "x-go-name": "URL"

View file

@ -4,9 +4,13 @@
package integration package integration
import ( import (
"bytes"
"fmt" "fmt"
"io"
"mime/multipart"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"testing" "testing"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
@ -38,12 +42,15 @@ func TestAPIListReleases(t *testing.T) {
case 1: case 1:
assert.False(t, release.IsDraft) assert.False(t, release.IsDraft)
assert.False(t, release.IsPrerelease) assert.False(t, release.IsPrerelease)
assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/1/assets"), release.UploadURL)
case 4: case 4:
assert.True(t, release.IsDraft) assert.True(t, release.IsDraft)
assert.False(t, release.IsPrerelease) assert.False(t, release.IsPrerelease)
assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/4/assets"), release.UploadURL)
case 5: case 5:
assert.False(t, release.IsDraft) assert.False(t, release.IsDraft)
assert.True(t, release.IsPrerelease) assert.True(t, release.IsPrerelease)
assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/5/assets"), release.UploadURL)
default: default:
assert.NoError(t, fmt.Errorf("unexpected release: %v", release)) assert.NoError(t, fmt.Errorf("unexpected release: %v", release))
} }
@ -248,3 +255,36 @@ func TestAPIDeleteReleaseByTagName(t *testing.T) {
req = NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag?token=%s", owner.Name, repo.Name, token)) req = NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag?token=%s", owner.Name, repo.Name, token))
_ = MakeRequest(t, req, http.StatusNoContent) _ = MakeRequest(t, req, http.StatusNoContent)
} }
func TestAPIUploadAssetRelease(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
session := loginUser(t, owner.LowerName)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
r := createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
filename := "image.png"
buff := generateImg()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("attachment", filename)
assert.NoError(t, err)
_, err = io.Copy(part, &buff)
assert.NoError(t, err)
err = writer.Close()
assert.NoError(t, err)
req := NewRequestWithBody(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets?name=test-asset&token=%s", owner.Name, repo.Name, r.ID, token), body)
req.Header.Add("Content-Type", writer.FormDataContentType())
resp := MakeRequest(t, req, http.StatusCreated)
var attachment *api.Attachment
DecodeJSON(t, resp, &attachment)
assert.EqualValues(t, "test-asset", attachment.Name)
assert.EqualValues(t, 104, attachment.Size)
}