242 lines
8.0 KiB
Go
242 lines
8.0 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/update"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type fakeReleaseFeed struct {
|
|
release update.Release
|
|
err error
|
|
calls int
|
|
}
|
|
|
|
var _ update.ReleaseFeed = &fakeReleaseFeed{}
|
|
|
|
func (f *fakeReleaseFeed) Latest() (update.Release, error) {
|
|
f.calls++
|
|
if f.err != nil {
|
|
return update.Release{}, f.err
|
|
}
|
|
return f.release, nil
|
|
}
|
|
|
|
type fakeUpdateStore struct {
|
|
result update.CheckResult
|
|
found bool
|
|
saveErr error
|
|
saves int
|
|
}
|
|
|
|
var _ update.StatusStore = &fakeUpdateStore{}
|
|
|
|
func (f *fakeUpdateStore) Load() (update.CheckResult, bool) {
|
|
return f.result, f.found
|
|
}
|
|
|
|
func (f *fakeUpdateStore) Save(result update.CheckResult) error {
|
|
f.saves++
|
|
if f.saveErr != nil {
|
|
return f.saveErr
|
|
}
|
|
f.result = result
|
|
f.found = true
|
|
return nil
|
|
}
|
|
|
|
var updateTestCheckedAt = time.Date(2026, 9, 30, 9, 41, 0, 0, time.UTC)
|
|
|
|
func newStoredUpdateResult() update.CheckResult {
|
|
return update.CheckResult{
|
|
Latest: "v2.3.0-beta",
|
|
ReleaseURL: "https://git.example/fsylvain/RenewCMS/releases/tag/v2.3.0-beta",
|
|
Notes: update.ReleaseNotes{
|
|
Version: "v2.3.0-beta",
|
|
ReleasedAt: time.Date(2026, 9, 24, 8, 0, 0, 0, time.UTC),
|
|
Items: []string{"Media library"},
|
|
},
|
|
CheckedAt: updateTestCheckedAt,
|
|
}
|
|
}
|
|
|
|
func TestVersionComparison(t *testing.T) {
|
|
cases := []struct {
|
|
current, latest string
|
|
available bool
|
|
}{
|
|
{"v2.2.0-beta", "v2.3.0-beta", true},
|
|
{"v2.5.0-beta", "v2.5.0", true},
|
|
{"v2.5.0", "v2.5.0-beta", false},
|
|
{"v2.9.0", "v2.10.0", true},
|
|
{"v2.5.0-beta", "v2.5.0-beta", false},
|
|
{"v2.5.0", "v2.4.9", false},
|
|
{"dev", "v9.9.9", false},
|
|
{"v2.5.0-beta.2", "v2.5.0-beta.10", true},
|
|
{"v2.5.0-alpha", "v2.5.0-beta", true},
|
|
{"2.0.0-beta", "v2.1.0-beta", true},
|
|
{"v2.5", "v2.5.0", false},
|
|
{"v2.2.0", "garbage", false},
|
|
{"v1.0.0-1", "v1.0.0-alpha", true},
|
|
{"v1.0.0-alpha", "v1.0.0-1", false},
|
|
{"v1.0.0-beta", "v1.0.0-beta.1", true},
|
|
{"v1.0.0-beta.1", "v1.0.0-beta", false},
|
|
{"v1.0.0-beta.2", "v1.0.0-beta.1", false},
|
|
{"v1.0.0-beta", "v1.0.0-alpha", false},
|
|
{"v1.0.0", "v1.0.0", false},
|
|
{"v1.1.0", "v1.0.9", false},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.current+" to "+c.latest, func(t *testing.T) {
|
|
if got := update.IsNewer(c.current, c.latest); got != c.available {
|
|
t.Errorf("IsNewer(%q, %q) = %v, want %v", c.current, c.latest, got, c.available)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseVersionRejectsGarbage(t *testing.T) {
|
|
for _, raw := range []string{"", "dev", "v", "v1.x.0", "v1.2.3-", "v-1.0.0"} {
|
|
if _, err := update.ParseVersion(raw); !errors.Is(err, update.ErrInvalidVersion) {
|
|
t.Errorf("ParseVersion(%q) error = %v, want ErrInvalidVersion", raw, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewestReleaseIsChosenRegardlessOfOrder(t *testing.T) {
|
|
releases := []update.Release{{Tag: "v2.4.0-beta"}, {Tag: "v2.5.0-beta"}, {Tag: "v2.3.0-beta"}}
|
|
newest, err := update.Newest(releases)
|
|
if err != nil || newest.Tag != "v2.5.0-beta" {
|
|
t.Fatalf("Newest = %q, %v, want v2.5.0-beta", newest.Tag, err)
|
|
}
|
|
}
|
|
|
|
func TestNewestReleaseOfNothingIsAnError(t *testing.T) {
|
|
if _, err := update.Newest(nil); !errors.Is(err, update.ErrNoRelease) {
|
|
t.Errorf("error = %v, want ErrNoRelease", err)
|
|
}
|
|
if _, err := update.Newest([]update.Release{{Tag: "nightly"}}); !errors.Is(err, update.ErrNoRelease) {
|
|
t.Errorf("error = %v, want ErrNoRelease for an unparsable tag", err)
|
|
}
|
|
}
|
|
|
|
func TestReleaseNotesBecomeAPlainTextList(t *testing.T) {
|
|
published := time.Date(2026, 9, 24, 8, 0, 0, 0, time.UTC)
|
|
body := "## What's new\n- **Media** library\n- Fixed [caching](http://x) bug\n\n* Faster start"
|
|
|
|
notes := update.BuildNotes("v2.3.0-beta", published, body)
|
|
|
|
want := []string{"Media library", "Fixed caching bug", "Faster start"}
|
|
if !reflect.DeepEqual(notes.Items, want) {
|
|
t.Errorf("items = %v, want %v", notes.Items, want)
|
|
}
|
|
if !notes.ReleasedAt.Equal(published) || notes.Version != "v2.3.0-beta" {
|
|
t.Errorf("notes = %+v", notes)
|
|
}
|
|
}
|
|
|
|
func TestReleaseNotesWithoutBulletsHaveNoItems(t *testing.T) {
|
|
notes := update.BuildNotes("v1.0.0", time.Time{}, "Just an intro.\n\n**Group**\n")
|
|
if notes.Items == nil || len(notes.Items) != 0 {
|
|
t.Errorf("items = %#v, want an empty non-nil list", notes.Items)
|
|
}
|
|
}
|
|
|
|
func TestReleaseNotesStripInlineMarkdown(t *testing.T) {
|
|
notes := update.BuildNotes("v1.0.0", time.Time{}, "- Run `task build` and *enjoy* __it__")
|
|
if want := []string{"Run task build and enjoy it"}; !reflect.DeepEqual(notes.Items, want) {
|
|
t.Errorf("items = %v, want %v", notes.Items, want)
|
|
}
|
|
}
|
|
|
|
func TestGetUpdateStatusUseCase(t *testing.T) {
|
|
t.Run("never checked", func(t *testing.T) {
|
|
status := useCases.NewGetUpdateStatusUseCase(&fakeUpdateStore{}, &fakeJobStore{}, &fakeHelperDetector{}, "v2.2.0-beta").GetStatus(true)
|
|
|
|
if status.Current != "v2.2.0-beta" || status.Latest != "" || status.Available {
|
|
t.Errorf("status = %+v", status)
|
|
}
|
|
if status.ReleaseURL != nil || status.Notes != nil || status.CheckedAt != nil || status.Progress != nil || status.Error != nil || status.StartedAt != nil {
|
|
t.Errorf("never-checked values must be nil: %+v", status)
|
|
}
|
|
if status.State != update.StateIdle || status.CanApply {
|
|
t.Errorf("state = %q, can_apply = %v", status.State, status.CanApply)
|
|
}
|
|
})
|
|
|
|
t.Run("stored result", func(t *testing.T) {
|
|
store := &fakeUpdateStore{result: newStoredUpdateResult(), found: true}
|
|
|
|
status := useCases.NewGetUpdateStatusUseCase(store, &fakeJobStore{}, &fakeHelperDetector{}, "v2.2.0-beta").GetStatus(true)
|
|
|
|
if status.Latest != "v2.3.0-beta" || !status.Available || status.CanApply || status.State != update.StateIdle {
|
|
t.Errorf("status = %+v", status)
|
|
}
|
|
if status.ReleaseURL == nil || *status.ReleaseURL != store.result.ReleaseURL {
|
|
t.Errorf("release_url = %v", status.ReleaseURL)
|
|
}
|
|
if status.CheckedAt == nil || !status.CheckedAt.Equal(updateTestCheckedAt) {
|
|
t.Errorf("checked_at = %v", status.CheckedAt)
|
|
}
|
|
})
|
|
|
|
t.Run("dev build never reports an update", func(t *testing.T) {
|
|
store := &fakeUpdateStore{result: newStoredUpdateResult(), found: true}
|
|
if useCases.NewGetUpdateStatusUseCase(store, &fakeJobStore{}, &fakeHelperDetector{}, "dev").GetStatus(true).Available {
|
|
t.Error("a dev build must not report an update")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCheckForUpdateUseCase(t *testing.T) {
|
|
newRelease := update.Release{Tag: "v2.3.0-beta", URL: "https://git.example/r/v2.3.0-beta", PublishedAt: updateTestCheckedAt}
|
|
now := func() time.Time { return updateTestCheckedAt }
|
|
|
|
t.Run("stores the result and reports it", func(t *testing.T) {
|
|
store := &fakeUpdateStore{}
|
|
feed := &fakeReleaseFeed{release: newRelease}
|
|
|
|
status, err := useCases.NewCheckForUpdateUseCase(feed, store, "v2.2.0-beta", now).Check()
|
|
|
|
if err != nil || !status.Available || status.Latest != "v2.3.0-beta" {
|
|
t.Fatalf("status = %+v, err = %v", status, err)
|
|
}
|
|
if store.result.Latest != "v2.3.0-beta" || store.result.ReleaseURL != newRelease.URL || !store.result.CheckedAt.Equal(updateTestCheckedAt) {
|
|
t.Errorf("stored = %+v", store.result)
|
|
}
|
|
})
|
|
|
|
t.Run("nothing newer", func(t *testing.T) {
|
|
feed := &fakeReleaseFeed{release: update.Release{Tag: "v2.2.0-beta"}}
|
|
status, err := useCases.NewCheckForUpdateUseCase(feed, &fakeUpdateStore{}, "v2.2.0-beta", now).Check()
|
|
if err != nil || status.Available {
|
|
t.Errorf("status = %+v, err = %v", status, err)
|
|
}
|
|
})
|
|
|
|
t.Run("feed failure keeps the previous result", func(t *testing.T) {
|
|
store := &fakeUpdateStore{result: newStoredUpdateResult(), found: true}
|
|
feed := &fakeReleaseFeed{err: update.ErrFeedUnavailable}
|
|
|
|
_, err := useCases.NewCheckForUpdateUseCase(feed, store, "v2.2.0-beta", now).Check()
|
|
|
|
if !errors.Is(err, update.ErrFeedUnavailable) {
|
|
t.Errorf("err = %v", err)
|
|
}
|
|
if store.saves != 0 || store.result.Latest != "v2.3.0-beta" {
|
|
t.Errorf("store changed: %+v saves %d", store.result, store.saves)
|
|
}
|
|
})
|
|
|
|
t.Run("store failure is returned", func(t *testing.T) {
|
|
store := &fakeUpdateStore{saveErr: errRepositoryFailure}
|
|
feed := &fakeReleaseFeed{release: newRelease}
|
|
if _, err := useCases.NewCheckForUpdateUseCase(feed, store, "v2.2.0-beta", now).Check(); !errors.Is(err, errRepositoryFailure) {
|
|
t.Errorf("err = %v", err)
|
|
}
|
|
})
|
|
}
|