Files
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

247 lines
7.4 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"net/http/httptest"
"testing"
"github.com/hanzoai/git/models/db"
repo_model "github.com/hanzoai/git/models/repo"
"github.com/hanzoai/git/models/unit"
"github.com/hanzoai/git/models/unittest"
"github.com/hanzoai/git/modules/test"
"github.com/hanzoai/git/modules/web"
"github.com/hanzoai/git/services/context"
"github.com/hanzoai/git/services/contexttest"
"github.com/hanzoai/git/services/forms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewReleasePost(t *testing.T) {
unittest.PrepareTestEnv(t)
get := func(t *testing.T, tagName string) *context.Context {
ctx, _ := contexttest.MockContext(t, "user2/repo1/releases/new?tag="+tagName)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
NewRelease(ctx)
return ctx
}
t.Run("NewReleasePage", func(t *testing.T) {
ctx := get(t, "v1.1")
assert.Empty(t, ctx.Data["ShowCreateTagOnlyButton"])
ctx = get(t, "new-tag-name")
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"])
})
post := func(t *testing.T, form forms.NewReleaseForm) (*context.Context, *httptest.ResponseRecorder) {
ctx, resp := contexttest.MockContext(t, "user2/repo1/releases/new")
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
web.SetForm(ctx, &form)
NewReleasePost(ctx)
return ctx, resp
}
loadRelease := func(t *testing.T, tagName string) *repo_model.Release {
return unittest.GetBean(t, &repo_model.Release{}, unittest.Cond("repo_id=1 AND tag_name=?", tagName))
}
t.Run("NewTagRelease", func(t *testing.T) {
post(t, forms.NewReleaseForm{
TagName: "newtag",
Target: "master",
Title: "title",
Content: "content",
})
rel := loadRelease(t, "newtag")
require.NotNil(t, rel)
assert.False(t, rel.IsTag)
assert.Equal(t, "master", rel.Target)
assert.Equal(t, "title", rel.Title)
assert.Equal(t, "content", rel.Note)
})
t.Run("ReleaseExistsDoUpdate(non-tag)", func(t *testing.T) {
_, resp := post(t, forms.NewReleaseForm{
TagName: "v1.1",
Target: "master",
Title: "updated-title",
Content: "updated-content",
})
rel := loadRelease(t, "v1.1")
require.NotNil(t, rel)
assert.False(t, rel.IsTag)
assert.Equal(t, "testing-release", rel.Title)
assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
})
t.Run("ReleaseExistsDoUpdate(tag-only)", func(t *testing.T) {
ctx, resp := post(t, forms.NewReleaseForm{
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
Target: "master",
Title: "updated-title",
Content: "updated-content",
TagOnly: true,
})
rel := loadRelease(t, "delete-tag")
require.NotNil(t, rel)
assert.True(t, rel.IsTag) // the record should not be updated because the request is "tag-only". TODO: need to improve the logic?
assert.Equal(t, "delete-tag", rel.Title)
assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
assert.NotEmpty(t, ctx.Data["ShowCreateTagOnlyButton"]) // still show the "tag-only" button
})
t.Run("ReleaseExistsDoUpdate(tag-release)", func(t *testing.T) {
ctx, _ := post(t, forms.NewReleaseForm{
TagName: "delete-tag", // a strange name, but it is the only "is_tag=true" fixture
Target: "master",
Title: "updated-title",
Content: "updated-content",
})
rel := loadRelease(t, "delete-tag")
require.NotNil(t, rel)
assert.False(t, rel.IsTag) // the tag has been "updated" to be a real "release"
assert.Equal(t, "updated-title", rel.Title)
assert.Empty(t, ctx.Flash.ErrorMsg)
})
t.Run("TagOnly", func(t *testing.T) {
ctx, _ := post(t, forms.NewReleaseForm{
TagName: "new-tag-only",
Target: "master",
Title: "title",
Content: "content",
TagOnly: true,
})
rel := loadRelease(t, "new-tag-only")
require.NotNil(t, rel)
assert.True(t, rel.IsTag)
assert.Empty(t, ctx.Flash.ErrorMsg)
})
t.Run("TagOnlyConflict", func(t *testing.T) {
_, resp := post(t, forms.NewReleaseForm{
TagName: "v1.1",
Target: "master",
Title: "title",
Content: "content",
TagOnly: true,
})
rel := loadRelease(t, "v1.1")
require.NotNil(t, rel)
assert.False(t, rel.IsTag)
assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
})
}
func TestCalReleaseNumCommitsBehind(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo-release/releases")
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 57)
contexttest.LoadGitRepo(t, ctx)
t.Cleanup(func() { ctx.Repo.GitRepo.Close() })
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeDrafts: ctx.Repo.Permission.CanWrite(unit.TypeReleases),
RepoID: ctx.Repo.Repository.ID,
})
assert.NoError(t, err)
countCache := make(map[string]int64)
for _, release := range releases {
err := calReleaseNumCommitsBehind(ctx, ctx.Repo, release, countCache)
assert.NoError(t, err)
}
type computedFields struct {
NumCommitsBehind int64
TargetBehind string
}
expectedComputation := map[string]computedFields{
"v1.0": {
NumCommitsBehind: 3,
TargetBehind: "main",
},
"v1.1": {
NumCommitsBehind: 1,
TargetBehind: "main",
},
"v2.0": {
NumCommitsBehind: 0,
TargetBehind: "main",
},
"non-existing-target-branch": {
NumCommitsBehind: 1,
TargetBehind: "main",
},
"empty-target-branch": {
NumCommitsBehind: 1,
TargetBehind: "main",
},
}
for _, r := range releases {
actual := computedFields{
NumCommitsBehind: r.NumCommitsBehind,
TargetBehind: r.TargetBehind,
}
assert.Equal(t, expectedComputation[r.TagName], actual, "wrong computed fields for %s: %#v", r.TagName, r)
}
}
// TestUpstreamReleases: a mirror caches git objects, not release assets. Copying
// them would duplicate gigabytes we already have a canonical copy of, into a
// cache that can only be older than the source — and showing an empty page is
// worse than either, because it says a project has no releases when it has four.
func TestUpstreamReleases(t *testing.T) {
for _, c := range []struct {
name string
repo repo_model.Repository
want string
}{
{
name: "mirror of github points at the source",
repo: repo_model.Repository{IsMirror: true, OriginalURL: "https://github.com/hanzoai/s3.git"},
want: "https://github.com/hanzoai/s3/releases",
},
{
name: "no .git suffix to strip",
repo: repo_model.Repository{IsMirror: true, OriginalURL: "https://github.com/hanzoai/s3"},
want: "https://github.com/hanzoai/s3/releases",
},
{
// Ours: the releases here ARE the canonical ones.
name: "not a mirror keeps its own page",
repo: repo_model.Repository{IsMirror: false, OriginalURL: "https://github.com/hanzoai/s3.git"},
want: "",
},
{
// An ssh or file upstream is not something a browser can be sent to.
name: "non-http upstream is not linkable",
repo: repo_model.Repository{IsMirror: true, OriginalURL: "[email protected]:hanzoai/s3.git"},
want: "",
},
{
name: "no upstream recorded",
repo: repo_model.Repository{IsMirror: true},
want: "",
},
} {
t.Run(c.name, func(t *testing.T) {
if got := upstreamReleases(&c.repo); got != c.want {
t.Errorf("upstreamReleases() = %q, want %q", got, c.want)
}
})
}
}