184 lines
6.9 KiB
Go
184 lines
6.9 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/update"
|
|
infraUpdate "RenewCMS/internal/infrastructure/update"
|
|
"crypto/ed25519"
|
|
"encoding/base64"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const giteaReleasesPath = "/api/v1/repos/fsylvain/RenewCMS/releases"
|
|
|
|
func newGiteaFeedServer(t *testing.T, status int, body string) (*httptest.Server, *[]string) {
|
|
t.Helper()
|
|
var paths []string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
paths = append(paths, r.URL.Path)
|
|
w.WriteHeader(status)
|
|
_, _ = w.Write([]byte(body))
|
|
}))
|
|
t.Cleanup(server.Close)
|
|
return server, &paths
|
|
}
|
|
|
|
func TestGiteaReleaseFeed(t *testing.T) {
|
|
list := `[
|
|
{"tag_name":"v2.4.0-beta","html_url":"https://git.example/r/v2.4.0-beta","body":"- Four","draft":false,"prerelease":true,"published_at":"2026-08-01T10:00:00Z"},
|
|
{"tag_name":"v9.0.0","html_url":"https://git.example/r/v9.0.0","body":"- Draft","draft":true,"prerelease":false,"published_at":"2026-09-30T10:00:00Z"},
|
|
{"tag_name":"v2.5.0-beta","html_url":"https://git.example/r/v2.5.0-beta","body":"## New\n- **Media** library\n- Fixed [caching](http://x) bug","draft":false,"prerelease":true,"published_at":"2026-09-24T08:30:00Z"},
|
|
{"tag_name":"v2.3.0-beta","html_url":"https://git.example/r/v2.3.0-beta","body":"","draft":false,"prerelease":true,"published_at":"2026-07-01T10:00:00Z"}
|
|
]`
|
|
|
|
t.Run("requests the release list and returns the newest published release", func(t *testing.T) {
|
|
server, paths := newGiteaFeedServer(t, http.StatusOK, list)
|
|
|
|
release, err := infraUpdate.NewGiteaReleaseFeed(server.URL + "/").Latest()
|
|
|
|
if err != nil {
|
|
t.Fatalf("Latest: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(*paths, []string{giteaReleasesPath}) {
|
|
t.Errorf("paths = %v", *paths)
|
|
}
|
|
if release.Tag != "v2.5.0-beta" || release.URL != "https://git.example/r/v2.5.0-beta" {
|
|
t.Errorf("release = %+v", release)
|
|
}
|
|
if want := []string{"Media library", "Fixed caching bug"}; !reflect.DeepEqual(release.Notes.Items, want) {
|
|
t.Errorf("items = %v", release.Notes.Items)
|
|
}
|
|
if want := time.Date(2026, 9, 24, 8, 30, 0, 0, time.UTC); !release.PublishedAt.Equal(want) || !release.Notes.ReleasedAt.Equal(want) {
|
|
t.Errorf("published = %v, notes released = %v", release.PublishedAt, release.Notes.ReleasedAt)
|
|
}
|
|
})
|
|
|
|
t.Run("a final release beats a beta of the same number", func(t *testing.T) {
|
|
server, _ := newGiteaFeedServer(t, http.StatusOK, `[{"tag_name":"v2.6.0-beta","published_at":"2026-09-01T00:00:00Z"},{"tag_name":"v2.6.0","published_at":"2026-09-02T00:00:00Z"}]`)
|
|
release, err := infraUpdate.NewGiteaReleaseFeed(server.URL).Latest()
|
|
if err != nil || release.Tag != "v2.6.0" {
|
|
t.Errorf("release = %+v, err = %v", release, err)
|
|
}
|
|
})
|
|
|
|
t.Run("only drafts is no release", func(t *testing.T) {
|
|
server, _ := newGiteaFeedServer(t, http.StatusOK, `[{"tag_name":"v9.0.0","draft":true}]`)
|
|
if _, err := infraUpdate.NewGiteaReleaseFeed(server.URL).Latest(); !errors.Is(err, update.ErrNoRelease) {
|
|
t.Errorf("err = %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("a 500 is a feed error", func(t *testing.T) {
|
|
server, _ := newGiteaFeedServer(t, http.StatusInternalServerError, "boom")
|
|
if _, err := infraUpdate.NewGiteaReleaseFeed(server.URL).Latest(); !errors.Is(err, update.ErrFeedUnavailable) {
|
|
t.Errorf("err = %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid json is a feed error", func(t *testing.T) {
|
|
server, _ := newGiteaFeedServer(t, http.StatusOK, "not json")
|
|
if _, err := infraUpdate.NewGiteaReleaseFeed(server.URL).Latest(); !errors.Is(err, update.ErrFeedUnavailable) {
|
|
t.Errorf("err = %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("an unreachable server is a feed error", func(t *testing.T) {
|
|
server, _ := newGiteaFeedServer(t, http.StatusOK, "[]")
|
|
url := server.URL
|
|
server.Close()
|
|
if _, err := infraUpdate.NewGiteaReleaseFeed(url).Latest(); !errors.Is(err, update.ErrFeedUnavailable) {
|
|
t.Errorf("err = %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestJSONStatusStore(t *testing.T) {
|
|
t.Run("The stored result survives a restart", func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "data", "update-status.json")
|
|
saved := newStoredUpdateResult()
|
|
if err := infraUpdate.NewJSONStatusStore(path).Save(saved); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
|
|
loaded, found := infraUpdate.NewJSONStatusStore(path).Load()
|
|
|
|
if !found {
|
|
t.Fatal("expected a stored result")
|
|
}
|
|
if loaded.Latest != saved.Latest || loaded.ReleaseURL != saved.ReleaseURL || !loaded.CheckedAt.Equal(saved.CheckedAt) {
|
|
t.Errorf("loaded = %+v", loaded)
|
|
}
|
|
if !reflect.DeepEqual(loaded.Notes.Items, saved.Notes.Items) {
|
|
t.Errorf("items = %v", loaded.Notes.Items)
|
|
}
|
|
})
|
|
|
|
t.Run("a second save replaces the first", func(t *testing.T) {
|
|
store := infraUpdate.NewJSONStatusStore(filepath.Join(t.TempDir(), "update-status.json"))
|
|
first := newStoredUpdateResult()
|
|
second := newStoredUpdateResult()
|
|
second.Latest = "v2.4.0-beta"
|
|
_ = store.Save(first)
|
|
_ = store.Save(second)
|
|
if loaded, _ := store.Load(); loaded.Latest != "v2.4.0-beta" {
|
|
t.Errorf("latest = %q", loaded.Latest)
|
|
}
|
|
})
|
|
|
|
t.Run("a missing file reads as no result", func(t *testing.T) {
|
|
if _, found := infraUpdate.NewJSONStatusStore(filepath.Join(t.TempDir(), "absent.json")).Load(); found {
|
|
t.Error("a missing file must read as no result")
|
|
}
|
|
})
|
|
|
|
t.Run("a corrupt file reads as no result", func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "update-status.json")
|
|
if err := os.WriteFile(path, []byte("{not json"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, found := infraUpdate.NewJSONStatusStore(path).Load(); found {
|
|
t.Error("a corrupt file must read as no result")
|
|
}
|
|
})
|
|
|
|
t.Run("an unwritable location is an error", func(t *testing.T) {
|
|
blocker := filepath.Join(t.TempDir(), "file")
|
|
if err := os.WriteFile(blocker, nil, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := infraUpdate.NewJSONStatusStore(filepath.Join(blocker, "update-status.json")).Save(newStoredUpdateResult()); err == nil {
|
|
t.Error("expected an error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCommittedReleasePublicKeyHasTheRightShape(t *testing.T) {
|
|
content, err := os.ReadFile(filepath.Join("..", "internal", "infrastructure", "update", "release.pub"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(content)))
|
|
if err != nil || len(decoded) != ed25519.PublicKeySize {
|
|
t.Fatalf("release.pub must be base64 of %d bytes: %v", ed25519.PublicKeySize, err)
|
|
}
|
|
if _, err := infraUpdate.EmbeddedPublicKey(); err != nil {
|
|
t.Errorf("embedded key: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGiteaReleaseFeedRejectsAnOversizedBody(t *testing.T) {
|
|
huge := `[{"tag_name":"v2.5.0","html_url":"u","body":"` + strings.Repeat("a", 5<<20) + `","draft":false,"published_at":"2026-09-24T08:30:00Z"}]`
|
|
server, _ := newGiteaFeedServer(t, http.StatusOK, huge)
|
|
|
|
if _, err := infraUpdate.NewGiteaReleaseFeed(server.URL).Latest(); !errors.Is(err, update.ErrFeedUnavailable) {
|
|
t.Fatalf("Latest error = %v, want ErrFeedUnavailable", err)
|
|
}
|
|
}
|