191 lines
7.0 KiB
Go
191 lines
7.0 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/api/pkg/apperr"
|
|
"encoding/json"
|
|
"net/http"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
)
|
|
|
|
func sizeSettingsBody(t *testing.T, body []byte) map[string]any {
|
|
t.Helper()
|
|
var decoded map[string]any
|
|
if err := json.Unmarshal(body, &decoded); err != nil {
|
|
t.Fatalf("invalid json %q: %v", body, err)
|
|
}
|
|
return decoded
|
|
}
|
|
|
|
func TestSettingsRetentionSizeHttp(t *testing.T) {
|
|
owner := func(e *activityHTTPEnv) activityRequest { return asUser(e, activityOwnerID) }
|
|
|
|
t.Run("Settings API, GET reports the size limit and the measured size", func(t *testing.T) {
|
|
cases := []struct {
|
|
days, gb int
|
|
bytes int64
|
|
next any
|
|
}{
|
|
{90, 0, 0, "2026-10-07T03:00:00+02:00"},
|
|
{90, 5, 3435973837, "2026-10-07T03:00:00+02:00"},
|
|
{0, 5, 3435973837, "2026-10-07T03:00:00+02:00"},
|
|
{0, 0, 1024, nil},
|
|
}
|
|
keys := []string{"version", "activity_retention_days", "activity_retention_max_gb", "activity_log_size_bytes", "activity_last_cleanup_at", "activity_last_cleanup_removed", "activity_next_cleanup_at"}
|
|
sort.Strings(keys)
|
|
for _, c := range cases {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
e.now = settingsTestClock()
|
|
e.settings.settings.RetentionDays = c.days
|
|
e.settings.settings.RetentionMaxGB = c.gb
|
|
e.probe.sizeBytes = &c.bytes
|
|
|
|
rec := e.get("/v1/settings", asUser(e, activityEditorID))
|
|
|
|
body := sizeSettingsBody(t, rec.Body.Bytes())
|
|
var got []string
|
|
for key := range body {
|
|
got = append(got, key)
|
|
}
|
|
sort.Strings(got)
|
|
if rec.Code != http.StatusOK || !reflect.DeepEqual(got, keys) {
|
|
t.Fatalf("unexpected %d %v", rec.Code, got)
|
|
}
|
|
if body["activity_retention_max_gb"] != float64(c.gb) || body["activity_log_size_bytes"] != float64(c.bytes) || body["activity_next_cleanup_at"] != c.next {
|
|
t.Errorf("%+v: unexpected body %v", c, body)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Settings API, a fresh install defaults to no size limit", func(t *testing.T) {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
|
|
body := sizeSettingsBody(t, e.get("/v1/settings", asUser(e, activityEditorID)).Body.Bytes())
|
|
|
|
if body["activity_retention_days"] != float64(90) || body["activity_retention_max_gb"] != float64(0) || body["activity_log_size_bytes"] != float64(0) {
|
|
t.Errorf("unexpected body %v", body)
|
|
}
|
|
})
|
|
|
|
t.Run("Settings API, PATCH saves a size limit, an age, or both", func(t *testing.T) {
|
|
cases := []struct {
|
|
body string
|
|
days, gb int
|
|
null bool
|
|
}{
|
|
{`{"activity_retention_max_gb": 5}`, 90, 5, false},
|
|
{`{"activity_retention_days": 0, "activity_retention_max_gb": 5}`, 0, 5, false},
|
|
{`{"activity_retention_days": 30}`, 30, 0, false},
|
|
{`{"activity_retention_days": 0}`, 0, 0, true},
|
|
}
|
|
for _, c := range cases {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
|
|
rec := e.send(http.MethodPatch, "/v1/settings", c.body, owner(e))
|
|
|
|
body := sizeSettingsBody(t, rec.Body.Bytes())
|
|
next, present := body["activity_next_cleanup_at"]
|
|
if rec.Code != http.StatusOK || body["activity_retention_days"] != float64(c.days) || body["activity_retention_max_gb"] != float64(c.gb) || !present || (next == nil) != c.null {
|
|
t.Errorf("%s: unexpected %d %v", c.body, rec.Code, body)
|
|
}
|
|
if e.settings.settings.RetentionDays != c.days || e.settings.settings.RetentionMaxGB != c.gb {
|
|
t.Errorf("%s: the repository holds %+v", c.body, e.settings.settings)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Settings API, leaving the size limit while Forever clears the schedule", func(t *testing.T) {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
e.settings.settings.RetentionDays = 0
|
|
e.settings.settings.RetentionMaxGB = 5
|
|
|
|
rec := e.send(http.MethodPatch, "/v1/settings", `{"activity_retention_max_gb": 0}`, owner(e))
|
|
|
|
body := sizeSettingsBody(t, rec.Body.Bytes())
|
|
next, present := body["activity_next_cleanup_at"]
|
|
if rec.Code != http.StatusOK || !present || next != nil {
|
|
t.Errorf("unexpected %d %v", rec.Code, body)
|
|
}
|
|
})
|
|
|
|
t.Run("Settings API, PATCH rejects invalid size input", func(t *testing.T) {
|
|
bodies := []string{
|
|
`{"activity_retention_max_gb": 2}`,
|
|
`{"activity_retention_max_gb": -1}`,
|
|
`{"activity_retention_max_gb": 100}`,
|
|
`{"activity_retention_max_gb": 5.5}`,
|
|
`{"activity_retention_max_gb": "5"}`,
|
|
`{"activity_retention_max_gb": null}`,
|
|
`{"activity_retention_days": 30, "activity_retention_max_gb": 2}`,
|
|
`{"activity_retention_days": null}`,
|
|
`{"activity_retention_days": 30, "activity_retention_max_gb": null}`,
|
|
`{"activity_retention_days": null, "activity_retention_max_gb": 5}`,
|
|
`[1]`,
|
|
`{}`,
|
|
`not json`,
|
|
}
|
|
for _, body := range bodies {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
e.settings.settings.RetentionMaxGB = 5
|
|
|
|
assertPlain(t, e.send(http.MethodPatch, "/v1/settings", body, owner(e)), http.StatusBadRequest, activityMismatch)
|
|
|
|
if e.settings.retentionOK != 0 || e.settings.settings.RetentionDays != 90 || e.settings.settings.RetentionMaxGB != 5 || len(e.all(t)) != 0 {
|
|
t.Errorf("%s: something was saved", body)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Settings API, a PATCH changing nothing is a no-op", func(t *testing.T) {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
e.settings.settings.RetentionMaxGB = 5
|
|
|
|
rec := e.send(http.MethodPatch, "/v1/settings", `{"activity_retention_days": 90, "activity_retention_max_gb": 5}`, owner(e))
|
|
|
|
if rec.Code != http.StatusOK || e.settings.retentionOK != 0 || len(e.all(t)) != 0 {
|
|
t.Errorf("unexpected %d, saves %d, events %d", rec.Code, e.settings.retentionOK, len(e.all(t)))
|
|
}
|
|
})
|
|
|
|
t.Run("Settings API, a non-owner cannot set the size limit", func(t *testing.T) {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
|
|
rec := e.send(http.MethodPatch, "/v1/settings", `{"activity_retention_max_gb": 5}`, asUser(e, activityAdminID))
|
|
|
|
assertPlain(t, rec, http.StatusForbidden, "Only the owner can do this.")
|
|
if e.settings.retentionOK != 0 {
|
|
t.Error("the admin changed something")
|
|
}
|
|
assertPlain(t, e.send(http.MethodPatch, "/v1/settings", `{"activity_retention_max_gb": 5}`), http.StatusUnauthorized, apperr.UnauthorizedErrorMessage)
|
|
})
|
|
|
|
t.Run("Settings API, a failing size measurement answers 500", func(t *testing.T) {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
e.probe.sizeErr = errRepositoryFailure
|
|
|
|
assertPlain(t, e.get("/v1/settings", asUser(e, activityEditorID)), http.StatusInternalServerError, "Internal server error")
|
|
})
|
|
|
|
t.Run("Settings API, the change is audited", func(t *testing.T) {
|
|
cases := []struct {
|
|
body string
|
|
params map[string]any
|
|
}{
|
|
{`{"activity_retention_days": 30}`, map[string]any{"from": float64(90), "to": float64(30)}},
|
|
{`{"activity_retention_max_gb": 5}`, map[string]any{"max_gb_from": float64(0), "max_gb_to": float64(5)}},
|
|
{`{"activity_retention_days": 30, "activity_retention_max_gb": 5}`, map[string]any{"from": float64(90), "to": float64(30), "max_gb_from": float64(0), "max_gb_to": float64(5)}},
|
|
}
|
|
for _, c := range cases {
|
|
e := newActivityHTTPEnv(t, 1000)
|
|
|
|
e.send(http.MethodPatch, "/v1/settings", c.body, owner(e))
|
|
|
|
events := e.all(t)
|
|
if len(events) != 1 || events[0].Action != "system.activity_retention_changed" || !reflect.DeepEqual(events[0].Params, c.params) {
|
|
t.Errorf("%s: unexpected events %+v", c.body, events)
|
|
}
|
|
}
|
|
})
|
|
}
|