mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
* feat(service): settings family — SettingsService over the Store seam The B3-8 settings/audit family's service: List, Patch (whitelist, boolean normalization, the require_2fa preconditions incl. the TOTP census and the unrelated-key guard, atomic apply, one audit row per changed key) and Setting (the read the hub and the backup scheduler consume; wraps db.ErrNotFound as the store reports it). db gains ApplySettings — the handler's raw upsert loop as one hand-written transactional wrapper where raw SQL belongs — and Store carries it. parseSettingsPatchBool duplicates auth.go's parseBooleanSettingValue with the admin surface's own pinned error wording; both messages are test-pinned, so the twins stay separate. Service-level characterization in settings_test.go mirrors the admin/api_test.go PATCH rows and adds the service-only contracts (ErrNotFound wrap, audit rows, multi-key apply). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 * refactor(admin): settings handlers thin over SettingsService; scheduler reads via it handleGetSettings/handlePatchSettings become adapters (decode, delegate, map ErrBadRequest to 400 with the service's prefix-free message); the whitelist and every precondition now live only in the service, so admin/types.go's copy is gone. MaintainBackups reads backup_schedule and backup_retention through the service — its backup mechanics keep the handle — and the maintenance chain threads Settings from the runtime the hub stage built. NewHandler/NewAdminAPI gain the settings parameter; all 207 construction sites wired via the newTestSettingsService helper. Behavior parity pinned by the existing TestAdminAPI_*Settings* rows (all green); the only unpinned change is the PATCH 500 path collapsing its four stage-specific internal messages into one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 * refactor(ws): hub settings cache reads through a SettingsReader The hub's server_name/motd cache consumes a consumer-side SettingsReader interface (service.SettingsService satisfies it; HubOptions.Settings is required and validated like DB and Limiter — the RequiredCollaborators pin gains the refusal case). hub_settings.go no longer touches db at all, so the import pin from the B3-5 finisher goes, and its allowlist row goes with it; the thinned admin settings handler's row is deleted too — two allowlist rows down, the settings family's persistence now lives only in db/ and service/. Test helpers (both ws package namespaces) default the reader over the test database; newBareHub wires it explicitly; production passes Services.Settings from StartRuntime. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 * docs(boundaries,b3): settings/audit family re-measure and evidence The backup pair takes its forecast boundary disposition; the family's two deleted rows and the disposition counts (28/18/15 -> 24/18/17) re-derived from the tool. Family evidence block appended to the B3-8 section; README B3 row records B3-5 complete and the family opened. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 * fix(service): prefix-free ErrBadRequest wraps for the pinned admin bodies The %.0w rework was meant to ride the service commit but was left unstaged: with the plain %w wrap the PATCH error bodies carry a 'bad request: ' prefix the admin pins reject. Zero-width wrapping keeps errors.Is(ErrBadRequest) while err.Error() stays exactly the pinned message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 * test(app): lifecycle hub fixtures wire the required Settings reader The two direct ws.NewHub sites in lifecycle_test predate Settings becoming required; race across internal/app is green again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 * test(db): cover ApplySettings — the db coverage floor caught the gap CI's coverage floor failed db at 78.9% against 79.3%: ApplySettings was exercised only from service tests, which do not count toward db's own figure. Four db-side rows cover the apply, the empty no-op, the in-transaction failure rollback and the begin failure, using the package's full-migration opener. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 * chore(coverage): raise the service floor to the branch's measured 69.2 The settings family's tested service code raised the Linux figure from the 67.8 floor to 69.2; the ratchet raises the floor in the same PR (service is not in the run-varying set). db stays at 79.3 — this PR restores its figure (79.5 with the ApplySettings tests), it did not set out to raise it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 --------- Co-authored-by: Claude <noreply@anthropic.com>
102 lines
4.0 KiB
Go
102 lines
4.0 KiB
Go
package admin_test
|
|
|
|
// Additional tests to increase branch coverage on adminAuthMiddleware,
|
|
// ownerOnlyMiddleware, and related helpers.
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/J3vb/OwnCord/Server/admin"
|
|
"github.com/J3vb/OwnCord/Server/auth"
|
|
)
|
|
|
|
// ─── adminAuthMiddleware edge cases ──────────────────────────────────────────
|
|
|
|
// TestAdminAuthMiddleware_ExpiredSession verifies that a valid token whose
|
|
// session has expired is rejected with 401.
|
|
func TestAdminAuthMiddleware_ExpiredSession(t *testing.T) {
|
|
database := openAdminTestDB(t)
|
|
handler := admin.NewAdminAPI(database, "1.0.0", nil, nil, nil, nil, nil, newTestModService(database), newTestRoleService(database), newTestSettingsService(database))
|
|
|
|
// Create a user and session, then manually expire the session by setting
|
|
// expires_at to a past timestamp via the exported Exec helper.
|
|
uid, err := database.CreateUser(context.Background(), "expireduser", "$2a$12$x", 1)
|
|
if err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
token := "expired-session-token"
|
|
tokenHash := auth.HashToken(token)
|
|
if _, err := database.CreateSession(context.Background(), uid, tokenHash, "test", "127.0.0.1"); err != nil {
|
|
t.Fatalf("CreateSession: %v", err)
|
|
}
|
|
|
|
// Set expires_at to yesterday so the session is treated as expired.
|
|
pastTime := time.Now().Add(-24 * time.Hour).UTC().Format("2006-01-02T15:04:05Z")
|
|
if _, err := database.ExecContext(context.Background(),
|
|
`UPDATE sessions SET expires_at = ? WHERE token = ?`,
|
|
pastTime, tokenHash,
|
|
); err != nil {
|
|
t.Fatalf("UPDATE sessions expires_at: %v", err)
|
|
}
|
|
|
|
w := doRequest(t, handler, http.MethodGet, "/stats", token, nil)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expired session status = %d, want 401; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestAdminAuthMiddleware_BannedAdmin verifies that a banned administrator's
|
|
// still-valid session is rejected with 403 — a ban must revoke admin-panel
|
|
// access immediately, not only when the session expires.
|
|
func TestAdminAuthMiddleware_BannedAdmin(t *testing.T) {
|
|
database := openAdminTestDB(t)
|
|
handler := admin.NewAdminAPI(database, "1.0.0", &mockHub{}, nil, nil, nil, nil, newTestModService(database), newTestRoleService(database), newTestSettingsService(database))
|
|
token := createAdminUser(t, database)
|
|
|
|
if w := doRequest(t, handler, http.MethodGet, "/stats", token, nil); w.Code != http.StatusOK {
|
|
t.Fatalf("pre-ban status = %d, want 200; body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
if _, err := database.ExecContext(context.Background(),
|
|
`UPDATE users SET banned = 1 WHERE username = 'adminuser'`,
|
|
); err != nil {
|
|
t.Fatalf("UPDATE users banned: %v", err)
|
|
}
|
|
|
|
w := doRequest(t, handler, http.MethodGet, "/stats", token, nil)
|
|
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("banned admin status = %d, want 403; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestAdminAuthMiddleware_MissingBearer verifies that a request with no
|
|
// Authorization header returns 401.
|
|
func TestAdminAuthMiddleware_MissingBearer(t *testing.T) {
|
|
database := openAdminTestDB(t)
|
|
handler := admin.NewAdminAPI(database, "1.0.0", nil, nil, nil, nil, nil, newTestModService(database), newTestRoleService(database), newTestSettingsService(database))
|
|
|
|
w := doRequest(t, handler, http.MethodGet, "/stats", "", nil)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("missing bearer status = %d, want 401", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestAdminAuthMiddleware_InvalidToken verifies that a token not in the
|
|
// sessions table returns 401.
|
|
func TestAdminAuthMiddleware_InvalidToken(t *testing.T) {
|
|
database := openAdminTestDB(t)
|
|
handler := admin.NewAdminAPI(database, "1.0.0", nil, nil, nil, nil, nil, newTestModService(database), newTestRoleService(database), newTestSettingsService(database))
|
|
|
|
w := doRequest(t, handler, http.MethodGet, "/stats", "completely-invalid-token", nil)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("invalid token status = %d, want 401", w.Code)
|
|
}
|
|
}
|