From 49656dee7f72a2c58231089e1d654b46828a8d55 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 24 Jun 2023 15:11:39 +0200 Subject: [PATCH] [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 074413a2dc2071ca580b2d45b3c26d448b9855c7) (cherry picked from commit 33feed47238aeef0e81517e72fdec29eba12b191) (cherry picked from commit 1ca21b95ffa433ea23e2946ad9ed1970244cbbe7) (cherry picked from commit 874f07cec2837bbbf43d55844bb65d1c68fbb207) (cherry picked from commit a78c538d8e4928d8d6a1c9ac8f8d2018a80a9a5c) (cherry picked from commit bef38ce382fe5ea6c8bacc5d71c666e14a42b8ad) (cherry picked from commit c8eb345354de5c79297e0e47502d7c3b2b3f790b) (cherry picked from commit b1efc0032a50b7eaa83c9000f70f1407dc065f95) --- models/repo/release.go | 5 ++++ modules/structs/release.go | 1 + services/convert/release.go | 1 + services/convert/release_test.go | 28 ++++++++++++++++++ templates/swagger/v1_json.tmpl | 4 +++ tests/integration/api_releases_test.go | 40 ++++++++++++++++++++++++++ 6 files changed, 79 insertions(+) create mode 100644 services/convert/release_test.go diff --git a/models/repo/release.go b/models/repo/release.go index a00585111e..191475d541 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -133,6 +133,11 @@ func (r *Release) HTMLURL() string { 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 func (r *Release) Link() string { return r.Repo.Link() + "/releases/tag/" + util.PathEscapeSegments(r.TagName) diff --git a/modules/structs/release.go b/modules/structs/release.go index 3fe40389b1..c7378645c2 100644 --- a/modules/structs/release.go +++ b/modules/structs/release.go @@ -18,6 +18,7 @@ type Release struct { HTMLURL string `json:"html_url"` TarURL string `json:"tarball_url"` ZipURL string `json:"zipball_url"` + UploadURL string `json:"upload_url"` IsDraft bool `json:"draft"` IsPrerelease bool `json:"prerelease"` // swagger:strfmt date-time diff --git a/services/convert/release.go b/services/convert/release.go index d8aa46d432..bfff53e62f 100644 --- a/services/convert/release.go +++ b/services/convert/release.go @@ -22,6 +22,7 @@ func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_mode HTMLURL: r.HTMLURL(), TarURL: r.TarURL(), ZipURL: r.ZipURL(), + UploadURL: r.APIUploadURL(), IsDraft: r.IsDraft, IsPrerelease: r.IsPrerelease, CreatedAt: r.CreatedUnix.AsTime(), diff --git a/services/convert/release_test.go b/services/convert/release_test.go new file mode 100644 index 0000000000..57d2dd415d --- /dev/null +++ b/services/convert/release_test.go @@ -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) +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 5e75f6f8b4..6b34ed6736 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -21007,6 +21007,10 @@ "type": "string", "x-go-name": "Target" }, + "upload_url": { + "type": "string", + "x-go-name": "UploadURL" + }, "url": { "type": "string", "x-go-name": "URL" diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index 7f43939083..526842d5ac 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -4,9 +4,13 @@ package integration import ( + "bytes" "fmt" + "io" + "mime/multipart" "net/http" "net/url" + "strings" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -38,12 +42,15 @@ func TestAPIListReleases(t *testing.T) { case 1: assert.False(t, release.IsDraft) assert.False(t, release.IsPrerelease) + assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/1/assets"), release.UploadURL) case 4: assert.True(t, release.IsDraft) assert.False(t, release.IsPrerelease) + assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/4/assets"), release.UploadURL) case 5: assert.False(t, release.IsDraft) assert.True(t, release.IsPrerelease) + assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/5/assets"), release.UploadURL) default: 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)) _ = 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) +}