199 lines
6.9 KiB
Go
199 lines
6.9 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/api"
|
|
validator "RenewCMS/api/pkg/validator"
|
|
"RenewCMS/internal/domain/update"
|
|
"RenewCMS/internal/infrastructure/config"
|
|
infraupdate "RenewCMS/internal/infrastructure/update"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-chi/jwtauth/v5"
|
|
)
|
|
|
|
type containerHarness struct {
|
|
*updateHarness
|
|
fetcher *fakeAssetFetcher
|
|
requests *fakeRestartRequestStore
|
|
jobs *fakeJobStore
|
|
}
|
|
|
|
func newContainerHarness(t *testing.T, container bool, helperInstalled bool) *containerHarness {
|
|
t.Helper()
|
|
validator.InitValidator()
|
|
rig := newDownloadRig(t)
|
|
rig.helper.installed = helperInstalled
|
|
helper := infraupdate.NewContainerHelperDetector(rig.helper, container)
|
|
now := func() time.Time { return updateTestCheckedAt }
|
|
token := jwtauth.New("HS256", []byte("test-secret"), nil)
|
|
feed := &fakeReleaseFeed{release: update.Release{
|
|
Tag: "v2.3.0-beta",
|
|
URL: "https://git.example/fsylvain/RenewCMS/releases/tag/v2.3.0-beta",
|
|
PublishedAt: time.Date(2026, 9, 24, 8, 0, 0, 0, time.UTC),
|
|
Notes: update.ReleaseNotes{Version: "v2.3.0-beta", Items: []string{"Media library"}},
|
|
}}
|
|
rig.store.found = true
|
|
start := useCases.NewStartUpdateDownloadUseCase(useCases.StartUpdateDownloadDependencies{
|
|
Store: rig.store,
|
|
Jobs: rig.jobs,
|
|
Helper: helper,
|
|
Results: rig.results,
|
|
Requests: rig.requests,
|
|
Fetcher: rig.fetcher,
|
|
Verifier: nil,
|
|
Runner: rig.runner,
|
|
Backup: rig.backup,
|
|
Staging: rig.staging,
|
|
Logger: rig.logger,
|
|
CurrentVersion: updateRunning,
|
|
Platform: rig.platform,
|
|
FeedURL: downloadFeedURL,
|
|
Now: now,
|
|
})
|
|
routes := &api.UseCases{
|
|
Token: token,
|
|
GetUserUseCase: useCases.NewGetUserUseCase(newUpdateUserRepo()),
|
|
GetUpdateStatusUseCase: useCases.NewGetUpdateStatusUseCase(rig.store, rig.jobs, helper, rig.results, rig.requests, updateRunning, container, now),
|
|
CheckForUpdateUseCase: useCases.NewCheckForUpdateUseCase(feed, rig.store, updateRunning, now),
|
|
StartUpdateDownloadUseCase: start,
|
|
RequestRestartUseCase: useCases.NewRequestRestartUseCase(rig.store, rig.jobs, helper, rig.results, rig.requests, updateRunning, now),
|
|
}
|
|
return &containerHarness{
|
|
updateHarness: &updateHarness{router: api.NewSystemRouter(routes), token: token, feed: feed, store: rig.store},
|
|
fetcher: rig.fetcher,
|
|
requests: rig.requests,
|
|
jobs: rig.jobs,
|
|
}
|
|
}
|
|
|
|
func TestContainerUpdateGuidanceHTTP(t *testing.T) {
|
|
t.Run("The status carries the container flag", func(t *testing.T) {
|
|
cases := []struct {
|
|
value string
|
|
flag bool
|
|
}{{"true", true}, {"TRUE", true}, {"false", false}, {"", false}, {"yes", false}}
|
|
for _, tc := range cases {
|
|
t.Run("value "+tc.value, func(t *testing.T) {
|
|
t.Setenv("SMTP_PORT", "1")
|
|
t.Setenv("AUTH_RATE_LIMIT_MAX_ATTEMPTS", "1")
|
|
t.Setenv("AUTH_RATE_LIMIT_WINDOW_SECONDS", "1")
|
|
t.Setenv("RENEWCMS_CONTAINER", tc.value)
|
|
settings, err := config.NewSettingsFromEnv()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if settings.Container != tc.flag {
|
|
t.Fatalf("Container = %v, want %v", settings.Container, tc.flag)
|
|
}
|
|
h := newContainerHarness(t, settings.Container, false)
|
|
|
|
body := decodeUpdateBody(t, h.sendAs(t, http.MethodGet, "/update", updateOwnerId))
|
|
|
|
assertUpdateKeys(t, body)
|
|
if body["container"] != tc.flag {
|
|
t.Errorf("container = %v, want %v", body["container"], tc.flag)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("Container mode disables apply even with a helper", func(t *testing.T) {
|
|
h := newContainerHarness(t, true, true)
|
|
h.store.result = newStoredUpdateResult()
|
|
|
|
for name, userId := range map[string]uint32{"owner": updateOwnerId, "admin": updateAdminId} {
|
|
body := decodeUpdateBody(t, h.sendAs(t, http.MethodGet, "/update", userId))
|
|
|
|
if body["can_apply"] != false || body["container"] != true {
|
|
t.Errorf("%s: body = %v", name, body)
|
|
}
|
|
if body["available"] != true || body["latest"] != "v2.3.0-beta" || body["notes"] == nil {
|
|
t.Errorf("%s: body = %v", name, body)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Host installs are unchanged", func(t *testing.T) {
|
|
h := newContainerHarness(t, false, true)
|
|
h.store.result = newStoredUpdateResult()
|
|
|
|
body := decodeUpdateBody(t, h.sendAs(t, http.MethodGet, "/update", updateOwnerId))
|
|
|
|
if body["container"] != false || body["can_apply"] != true {
|
|
t.Errorf("body = %v", body)
|
|
}
|
|
})
|
|
|
|
t.Run("The check still works in a container", func(t *testing.T) {
|
|
h := newContainerHarness(t, true, true)
|
|
h.store.found = false
|
|
|
|
rec := h.sendAs(t, http.MethodPost, "/update/check", updateOwnerId)
|
|
|
|
body := decodeUpdateBody(t, rec)
|
|
if rec.Code != http.StatusOK || body["available"] != true || body["container"] != true {
|
|
t.Fatalf("status = %d body = %v", rec.Code, body)
|
|
}
|
|
if h.store.saves != 1 || h.store.result.Latest != "v2.3.0-beta" {
|
|
t.Errorf("saves = %d result = %+v", h.store.saves, h.store.result)
|
|
}
|
|
})
|
|
|
|
t.Run("Download and restart are refused in a container", func(t *testing.T) {
|
|
for _, path := range []string{"/update/download", "/update/restart"} {
|
|
t.Run(path, func(t *testing.T) {
|
|
h := newContainerHarness(t, true, true)
|
|
h.store.result = newStoredUpdateResult()
|
|
|
|
rec := h.sendAs(t, http.MethodPost, path, updateOwnerId)
|
|
|
|
if rec.Code != http.StatusConflict {
|
|
t.Fatalf("status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
if _, found := h.jobs.Load(); found {
|
|
t.Error("no job may start")
|
|
}
|
|
if len(h.fetcher.calls) != 0 {
|
|
t.Errorf("fetcher calls = %v", h.fetcher.calls)
|
|
}
|
|
if h.requests.writes != 0 {
|
|
t.Errorf("restart request writes = %d", h.requests.writes)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestContainerHelperDetectorTruthTable(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
container, inner, want bool
|
|
}{{false, false, false}, {false, true, true}, {true, false, false}, {true, true, false}} {
|
|
got := infraupdate.NewContainerHelperDetector(&fakeHelperDetector{installed: tc.inner}, tc.container).Installed()
|
|
if got != tc.want {
|
|
t.Errorf("container=%v inner=%v: Installed() = %v, want %v", tc.container, tc.inner, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContainerStatusUseCaseCanApplyTruthTable(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
isOwner, container, helper, want bool
|
|
}{
|
|
{true, false, true, true},
|
|
{false, false, true, false},
|
|
{true, true, true, false},
|
|
{true, false, false, false},
|
|
{false, true, true, false},
|
|
} {
|
|
rig := newDownloadRig(t)
|
|
rig.helper.installed = tc.helper
|
|
status := useCases.NewGetUpdateStatusUseCase(rig.store, rig.jobs, infraupdate.NewContainerHelperDetector(rig.helper, tc.container), rig.results, rig.requests, updateRunning, tc.container, func() time.Time { return updateTestCheckedAt }).GetStatus(tc.isOwner)
|
|
if status.CanApply != tc.want || status.Container != tc.container {
|
|
t.Errorf("%+v: CanApply=%v Container=%v", tc, status.CanApply, status.Container)
|
|
}
|
|
}
|
|
}
|