Files
Anand d9b2519b75 Fleet audit fixes: PBS page, shared SSE, safer ops, deep links + comment cleanup
- New PBS Backups page (/pbs): datastore usage, group browser, per-group
  prune, GC with live status, sync/verify jobs with admin-gated run
- Shared single-connection SSE store (lib/sse.ts) feeding NotificationBell
  and the telemetry pill; global offline banner with reconnect invalidation
- Correctness: CIFS storage credentials sent in JSON body; per-row alert
  silencing + unsilence endpoint; graceful stop-and-delete for running
  guests; accent/shadow token fixes for all look presets
- Safer operations: confirms for SDN apply and host upgrade-all; Refresh
  control on every page; 30s polling added to previously-static pages
- Wayfinding: guests deep-linkable via /inventory?focusGuest, scoped
  inventory views, topology guest click-through, Overview links
- Forms/consistency: node storage/bridge pick-lists in CreateGuestDialog,
  backup schedule validation, BulkOperationsPage on shared DataTable,
  progressive pagination on cluster access lists, shared Timestamp and
  chartToneFor helpers, editable alert rules and webhooks
- Security/tests: CSRF, authz-matrix, redaction and PBS test coverage;
  webhook outbox + connection TLS fingerprint migrations (00032/00033,
  sqlite + postgres)
- Comment audit: removed stale, duplicated and orphaned comments; corrected
  inaccurate doc comments (UpdateGuestConfig, InvalidateAll, breakpoint
  references); no behavior changes
2026-09-13 23:29:33 +05:30

98 lines
2.6 KiB
Go

package auth
import (
"context"
"path/filepath"
"testing"
"ferrum/internal/config"
"ferrum/internal/store"
)
func newTestService(t *testing.T) *Service {
t.Helper()
dbPath := filepath.Join(t.TempDir(), "ferrum.db")
db, err := store.Open(config.DBConfig{Driver: "sqlite", Path: dbPath})
if err != nil {
t.Fatalf("store.Open: %v", err)
}
t.Cleanup(func() { db.Close() })
return NewService(db, nil)
}
func TestBootstrapCreatesFirstAdminOnly(t *testing.T) {
svc := newTestService(t)
ctx := context.Background()
needsSetup, err := svc.NeedsSetup(ctx)
if err != nil {
t.Fatalf("NeedsSetup: %v", err)
}
if !needsSetup {
t.Fatal("expected NeedsSetup to be true on a fresh database")
}
user, err := svc.Bootstrap(ctx, "admin", "admin@example.com", "supersecret1")
if err != nil {
t.Fatalf("Bootstrap: %v", err)
}
if !user.IsAdmin {
t.Fatal("bootstrapped user must be an admin")
}
needsSetup, err = svc.NeedsSetup(ctx)
if err != nil {
t.Fatalf("NeedsSetup after bootstrap: %v", err)
}
if needsSetup {
t.Fatal("expected NeedsSetup to be false after bootstrap")
}
if _, err := svc.Bootstrap(ctx, "second", "second@example.com", "supersecret1"); err == nil {
t.Fatal("expected a second Bootstrap call to fail")
}
}
func TestLoginRoundTrip(t *testing.T) {
svc := newTestService(t)
ctx := context.Background()
if _, err := svc.Bootstrap(ctx, "admin", "admin@example.com", "correct-password"); err != nil {
t.Fatalf("Bootstrap: %v", err)
}
if _, _, err := svc.Login(ctx, "admin", "wrong-password", "127.0.0.1", "test-agent"); err != ErrInvalidCredentials {
t.Fatalf("Login with wrong password: got %v, want ErrInvalidCredentials", err)
}
user, token, err := svc.Login(ctx, "admin", "correct-password", "127.0.0.1", "test-agent")
if err != nil {
t.Fatalf("Login: %v", err)
}
if token == "" {
t.Fatal("expected a non-empty session token")
}
authed, err := svc.Authenticate(ctx, token)
if err != nil {
t.Fatalf("Authenticate: %v", err)
}
if authed.ID != user.ID {
t.Fatalf("Authenticate returned user %q, want %q", authed.ID, user.ID)
}
if _, err := svc.Logout(ctx, token); err != nil {
t.Fatalf("Logout: %v", err)
}
if _, err := svc.Authenticate(ctx, token); err != ErrInvalidCredentials {
t.Fatalf("Authenticate after logout: got %v, want ErrInvalidCredentials", err)
}
}
func TestAuthenticateRejectsUnknownToken(t *testing.T) {
svc := newTestService(t)
if _, err := svc.Authenticate(context.Background(), "not-a-real-token"); err != ErrInvalidCredentials {
t.Fatalf("Authenticate(unknown token): got %v, want ErrInvalidCredentials", err)
}
}