Files
llmgw/internal/web/admin_settings_test.go
Ilker ManapandClaude Opus 5.5 659f20dc9a The cluster's settings edited on the portal, by administrators
/admin/settings edits the document llmgw ctl config edit opens: checked as a
control plane would load it, refused when made from a version since replaced
(the edit kept), its history viewed and rolled back as a new version, every
change audited by size and never by contents, since it can hold webhook
secrets. Administrators only; in English and Turkish.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 17:23:13 +02:00

144 lines
5.3 KiB
Go

package web
import (
"context"
"errors"
"io"
"log/slog"
"net/http"
"net/url"
"strings"
"testing"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
type fakeSettings struct{ versions []store.SettingsVersion }
func (f *fakeSettings) ClusterSettings(context.Context) (store.SettingsVersion, error) {
if len(f.versions) == 0 {
return store.SettingsVersion{}, nil
}
return f.versions[len(f.versions)-1], nil
}
func (f *fakeSettings) ClusterSettingsAt(_ context.Context, v int64) (store.SettingsVersion, error) {
for _, s := range f.versions {
if s.Version == v {
return s, nil
}
}
return store.SettingsVersion{}, nil
}
func (f *fakeSettings) ClusterSettingsHistory(context.Context, int) ([]store.SettingsVersion, error) {
out := make([]store.SettingsVersion, len(f.versions))
for i, v := range f.versions {
out[len(f.versions)-1-i] = v
}
return out, nil
}
func (f *fakeSettings) PutClusterSettings(_ context.Context, base int64, body, by, note string) (int64, error) {
cur, _ := f.ClusterSettings(context.Background())
if base != cur.Version {
return 0, store.ErrSettingsChanged
}
n := cur.Version + 1
f.versions = append(f.versions, store.SettingsVersion{Version: n, Body: body, By: by, Note: note, At: time.Now()})
return n, nil
}
type auditSink struct{ events []store.AuditEvent }
func (a *auditSink) WriteAudit(_ context.Context, e store.AuditEvent) error {
a.events = append(a.events, e)
return nil
}
func clusterSettingsPage(t *testing.T, user store.User) (*http.ServeMux, *fakeSettings, *auditSink) {
t.Helper()
s := &fakeSettings{versions: []store.SettingsVersion{{Version: 1, Body: "guard:\n enabled: true\n", By: "init", At: time.Now()}}}
p := NewAccountPages(fakeAccounts{user: user}, nil, nil, "v0", slog.New(slog.NewTextHandler(io.Discard, nil)))
sink := &auditSink{}
p.SetAudit(sink)
mux := http.NewServeMux()
p.RegisterAdminSettings(mux, s, func(b []byte) error {
if strings.Contains(string(b), "bogus") {
return errors.New("unknown key bogus")
}
return nil
}, []string{"listen", "store"})
return mux, s, sink
}
func TestTheSettingsPageIsForAdministratorsOnly(t *testing.T) {
for _, u := range []store.User{
{ID: "u1", Email: "v@example.com", Role: store.RoleViewer},
{ID: "u2", Email: "o@example.com", Role: store.RoleOperator},
{ID: "u3", Email: "b@example.com", Role: store.RoleBilling},
} {
mux, _, _ := clusterSettingsPage(t, u)
if w := mirrorGet(t, mux, "/admin/settings"); w.Code != http.StatusNotFound {
t.Errorf("%s saw the settings: %d", u.Role, w.Code)
}
}
mux, _, _ := clusterSettingsPage(t, store.User{ID: "a", Email: "a@example.com", IsAdmin: true})
body := mirrorGet(t, mux, "/admin/settings").Body.String()
for _, want := range []string{"guard:\n enabled: true", "Version 1, in force", "<code>listen</code>", `href="/admin/settings" aria-current="page"`} {
if !strings.Contains(body, want) {
t.Errorf("no %q in:\n%s", want, body)
}
}
}
func TestSettingsAreCheckedAppliedAndRolledBack(t *testing.T) {
mux, s, sink := clusterSettingsPage(t, store.User{ID: "a", Email: "a@example.com", IsAdmin: true})
post := func(form url.Values) string {
form.Set("csrf", "tok")
return mirrorPost(t, mux, "/admin/settings", form).Body.String()
}
// Refused: does not load. The edit stays in the editor; nothing kept.
body := post(url.Values{"base": {"1"}, "body": {"bogus: 1\n"}})
if !strings.Contains(body, "unknown key bogus") || !strings.Contains(body, "bogus: 1") || len(s.versions) != 1 {
t.Errorf("an invalid document: %d versions\n%s", len(s.versions), body)
}
// Applied, with its note and who.
body = post(url.Values{"base": {"1"}, "body": {"guard:\r\n enabled: false\r\n"}, "note": {"maintenance"}})
if !strings.Contains(body, "Version 2 is in force") || len(s.versions) != 2 ||
s.versions[1].Body != "guard:\n enabled: false\n" || s.versions[1].By != "a@example.com" || s.versions[1].Note != "maintenance" {
t.Errorf("applying: %+v\n%s", s.versions, body)
}
// Made from a version no longer the newest: refused, not overwritten.
body = post(url.Values{"base": {"1"}, "body": {"guard:\n enabled: true\n x: 1\n"}})
if !strings.Contains(body, "someone changed the settings") || len(s.versions) != 2 {
t.Errorf("a stale edit: %d versions\n%s", len(s.versions), body)
}
// An earlier version is shown, and put back as a new one.
if body := mirrorGet(t, mux, "/admin/settings?version=1").Body.String(); !strings.Contains(body, "Put version 1 back in force") {
t.Errorf("viewing version 1:\n%s", body)
}
w := mirrorPost(t, mux, "/admin/settings/rollback", url.Values{"csrf": {"tok"}, "version": {"1"}})
if len(s.versions) != 3 || s.versions[2].Body != s.versions[0].Body || !strings.Contains(w.Body.String(), "as version 3") {
t.Errorf("rollback: %+v\n%s", s.versions, w.Body.String())
}
// Without the CSRF token, nothing.
if w := mirrorPost(t, mux, "/admin/settings", url.Values{"base": {"3"}, "body": {"a: 1\n"}}); w.Code != http.StatusBadRequest || len(s.versions) != 3 {
t.Errorf("no token: %d", w.Code)
}
// Audited, without the document itself: it can hold secrets.
var saw bool
for _, e := range sink.events {
if strings.Contains(e.Detail, "enabled") {
t.Errorf("the document is in the audit log: %+v", e)
}
if e.Action == "POST /admin/settings" && strings.Contains(e.Detail, "version 2") {
saw = true
}
}
if !saw {
t.Errorf("no audit line for version 2: %+v", sink.events)
}
}