Files
sub2api-overdraft/backend/internal/service/update_service_test.go

270 lines
8.5 KiB
Go

//go:build unit
package service
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/require"
)
type updateServiceCacheStub struct {
data string
}
func (s *updateServiceCacheStub) GetUpdateInfo(context.Context) (string, error) {
if s.data == "" {
return "", errors.New("cache miss")
}
return s.data, nil
}
func (s *updateServiceCacheStub) SetUpdateInfo(_ context.Context, data string, _ time.Duration) error {
s.data = data
return nil
}
type updateServiceGitHubClientStub struct {
release *GitHubRelease
recentReleases []*GitHubRelease
recentErr error
repositoryFile []byte
repositoryFileErr error
latestRepo string
fileRepo string
fileRef string
filePath string
fileCalls int
}
func (s *updateServiceGitHubClientStub) FetchLatestRelease(_ context.Context, repo string) (*GitHubRelease, error) {
s.latestRepo = repo
return s.release, nil
}
func (s *updateServiceGitHubClientStub) FetchRecentReleases(context.Context, string, int) ([]*GitHubRelease, error) {
return s.recentReleases, s.recentErr
}
func (s *updateServiceGitHubClientStub) FetchRepositoryFile(_ context.Context, repo, ref, filePath string) ([]byte, error) {
s.fileCalls++
s.fileRepo = repo
s.fileRef = ref
s.filePath = filePath
return s.repositoryFile, s.repositoryFileErr
}
func (s *updateServiceGitHubClientStub) DownloadFile(context.Context, string, string, int64) error {
panic("DownloadFile should not be called when no update is available")
}
func (s *updateServiceGitHubClientStub) FetchChecksumFile(context.Context, string) ([]byte, error) {
panic("FetchChecksumFile should not be called when no update is available")
}
func TestUpdateServicePerformUpdateNoUpdateReturnsSentinel(t *testing.T) {
svc := NewUpdateService(
&updateServiceCacheStub{},
&updateServiceGitHubClientStub{
release: &GitHubRelease{
TagName: "v0.1.132",
Name: "v0.1.132",
},
},
"0.1.132",
"release",
)
err := svc.PerformUpdate(context.Background())
require.Error(t, err)
require.True(t, errors.Is(err, ErrNoUpdateAvailable))
require.ErrorIs(t, err, ErrNoUpdateAvailable)
}
func TestUpdateServiceSourceBuildTracksForkVersionFile(t *testing.T) {
client := &updateServiceGitHubClientStub{repositoryFile: []byte("0.1.176-overdraft.2\n")}
svc := NewUpdateService(
&updateServiceCacheStub{},
client,
"0.1.176-overdraft.1",
"source",
)
info, err := svc.CheckUpdate(context.Background(), true)
require.NoError(t, err)
require.Equal(t, "0.1.176-overdraft.1", info.CurrentVersion)
require.Equal(t, "0.1.176-overdraft.2", info.LatestVersion)
require.True(t, info.HasUpdate)
require.Equal(t, "source", info.BuildType)
require.Equal(t, githubSourceUpdateURL, info.ReleaseInfo.HTMLURL)
require.Equal(t, githubRepo, client.fileRepo)
require.Equal(t, githubSourceBranch, client.fileRef)
require.Equal(t, githubForkVersionFile, client.filePath)
require.Empty(t, client.latestRepo, "源码构建不能查询官方或 Fork 的二进制 Release")
}
func TestUpdateServiceSourceBuildIgnoresLegacyOfficialCache(t *testing.T) {
cache := &updateServiceCacheStub{data: `{"latest":"0.1.176","timestamp":4102444800}`}
client := &updateServiceGitHubClientStub{repositoryFile: []byte("0.1.176-overdraft.1\n")}
svc := NewUpdateService(cache, client, "0.1.176-overdraft.1", "source")
info, err := svc.CheckUpdate(context.Background(), false)
require.NoError(t, err)
require.False(t, info.HasUpdate)
require.Equal(t, "0.1.176-overdraft.1", info.LatestVersion)
require.Equal(t, 1, client.fileCalls)
}
func TestUpdateServiceSourceBuildRejectsBinaryUpdate(t *testing.T) {
svc := NewUpdateService(
&updateServiceCacheStub{},
&updateServiceGitHubClientStub{},
"0.1.176-overdraft.1",
"source",
)
err := svc.PerformUpdate(context.Background())
require.ErrorIs(t, err, ErrSourceBuildUpdateRequired)
}
func TestUpdateServiceReleaseBuildUsesForkRepository(t *testing.T) {
client := &updateServiceGitHubClientStub{release: &GitHubRelease{TagName: "v0.1.177-overdraft.1"}}
svc := NewUpdateService(&updateServiceCacheStub{}, client, "0.1.176-overdraft.1", "release")
info, err := svc.CheckUpdate(context.Background(), true)
require.NoError(t, err)
require.True(t, info.HasUpdate)
require.Equal(t, githubRepo, client.latestRepo)
}
func TestCompareVersionsSupportsForkPrereleaseRevisions(t *testing.T) {
require.Less(t, compareVersions("0.1.176-overdraft.1", "0.1.176-overdraft.2"), 0)
require.Zero(t, compareVersions("v0.1.176-overdraft.2", "0.1.176-overdraft.2"))
require.Greater(t, compareVersions("0.1.177-overdraft.1", "0.1.176-overdraft.9"), 0)
}
func newRollbackTestService(current string, releases []*GitHubRelease) *UpdateService {
return NewUpdateService(
&updateServiceCacheStub{},
&updateServiceGitHubClientStub{recentReleases: releases},
current,
"release",
)
}
func TestUpdateServiceListRollbackVersionsFiltersAndCaps(t *testing.T) {
releases := []*GitHubRelease{
{TagName: "v0.1.148", PublishedAt: "2026-07-09T00:00:00Z"}, // newer than current: excluded
{TagName: "v0.1.147", PublishedAt: "2026-07-08T00:00:00Z"}, // current: excluded
{TagName: "v0.1.146-rc1", PublishedAt: "2026-07-07T12:00:00Z", Prerelease: true}, // prerelease: excluded
{TagName: "v0.1.146", PublishedAt: "2026-07-07T00:00:00Z"},
{TagName: "v0.1.145", PublishedAt: "2026-07-06T00:00:00Z", Draft: true}, // draft: excluded
{TagName: "v0.1.144", PublishedAt: "2026-07-05T00:00:00Z"},
{TagName: "v0.1.144", PublishedAt: "2026-07-05T00:00:00Z"}, // duplicate: excluded
{TagName: "v0.1.143", PublishedAt: "2026-07-04T00:00:00Z"},
{TagName: "v0.1.142", PublishedAt: "2026-07-03T00:00:00Z"}, // beyond cap of 3: excluded
}
svc := newRollbackTestService("0.1.147", releases)
versions, err := svc.ListRollbackVersions(context.Background())
require.NoError(t, err)
require.Len(t, versions, 3)
require.Equal(t, "0.1.146", versions[0].Version)
require.Equal(t, "0.1.144", versions[1].Version)
require.Equal(t, "0.1.143", versions[2].Version)
}
func TestUpdateServiceListRollbackVersionsSortsUnorderedInput(t *testing.T) {
releases := []*GitHubRelease{
{TagName: "v0.1.144"},
{TagName: "v0.1.146"},
{TagName: "v0.1.145"},
}
svc := newRollbackTestService("0.1.147", releases)
versions, err := svc.ListRollbackVersions(context.Background())
require.NoError(t, err)
require.Len(t, versions, 3)
require.Equal(t, "0.1.146", versions[0].Version)
require.Equal(t, "0.1.145", versions[1].Version)
require.Equal(t, "0.1.144", versions[2].Version)
}
func TestUpdateServiceListRollbackVersionsEmptyWhenNoneOlder(t *testing.T) {
releases := []*GitHubRelease{
{TagName: "v0.1.147"},
{TagName: "v0.1.148"},
}
svc := newRollbackTestService("0.1.147", releases)
versions, err := svc.ListRollbackVersions(context.Background())
require.NoError(t, err)
require.Empty(t, versions)
}
func TestUpdateServiceListRollbackVersionsPropagatesFetchError(t *testing.T) {
svc := NewUpdateService(
&updateServiceCacheStub{},
&updateServiceGitHubClientStub{recentErr: errors.New("github unavailable")},
"0.1.147",
"release",
)
_, err := svc.ListRollbackVersions(context.Background())
require.Error(t, err)
require.Contains(t, err.Error(), "github unavailable")
}
func TestUpdateServiceRollbackToVersionRejectsDisallowedTargets(t *testing.T) {
releases := []*GitHubRelease{
{TagName: "v0.1.148"},
{TagName: "v0.1.147"},
{TagName: "v0.1.146"},
{TagName: "v0.1.145"},
{TagName: "v0.1.144"},
{TagName: "v0.1.143"},
{TagName: "v0.1.142"},
}
svc := newRollbackTestService("0.1.147", releases)
for _, target := range []string{
"", // empty
"0.1.147", // current version
"v0.1.147", // current version with prefix
"0.1.148", // newer than current
"0.1.142", // older than the 3 most recent
"9.9.9", // nonexistent
} {
err := svc.RollbackToVersion(context.Background(), target)
require.ErrorIs(t, err, ErrRollbackVersionNotAllowed, "target %q should be rejected", target)
}
}
func TestUpdateServiceRollbackToVersionAcceptsVPrefix(t *testing.T) {
// No platform asset in the release: the target passes the allowlist check
// and fails later at asset lookup, proving the version itself was accepted.
releases := []*GitHubRelease{
{TagName: "v0.1.147"},
{TagName: "v0.1.146"},
}
svc := newRollbackTestService("0.1.147", releases)
err := svc.RollbackToVersion(context.Background(), "v0.1.146")
require.Error(t, err)
require.NotErrorIs(t, err, ErrRollbackVersionNotAllowed)
require.Contains(t, err.Error(), "no compatible release found")
}