package server import ( "context" "net/http" "strings" "testing" "galaxy/backend/internal/admin" "galaxy/backend/internal/adminconsole" "galaxy/backend/internal/server/middleware/basicauth" "go.uber.org/zap" ) type fakeOperatorAdmin struct { list []admin.Admin createErr error created admin.CreateInput createCalls int disableCalls int enableCalls int resetCalls int lastResetUser string lastResetPass string } func (f *fakeOperatorAdmin) List(context.Context) ([]admin.Admin, error) { return f.list, nil } func (f *fakeOperatorAdmin) Create(_ context.Context, in admin.CreateInput) (admin.Admin, error) { f.createCalls++ f.created = in if f.createErr != nil { return admin.Admin{}, f.createErr } return admin.Admin{Username: in.Username}, nil } func (f *fakeOperatorAdmin) Disable(_ context.Context, username string) (admin.Admin, error) { f.disableCalls++ return admin.Admin{Username: username}, nil } func (f *fakeOperatorAdmin) Enable(_ context.Context, username string) (admin.Admin, error) { f.enableCalls++ return admin.Admin{Username: username}, nil } func (f *fakeOperatorAdmin) ResetPassword(_ context.Context, username, password string) (admin.Admin, error) { f.resetCalls++ f.lastResetUser = username f.lastResetPass = password return admin.Admin{Username: username}, nil } func operatorsRouter(t *testing.T, operators OperatorAdmin) (http.Handler, *adminconsole.CSRF) { t.Helper() csrf := adminconsole.NewCSRF([]byte("test-key")) handler, err := NewRouter(RouterDependencies{ Logger: zap.NewNop(), AdminVerifier: basicauth.NewStaticVerifier("secret"), AdminConsole: NewAdminConsoleHandlers(AdminConsoleDeps{CSRF: csrf, Operators: operators}), }) if err != nil { t.Fatalf("NewRouter: %v", err) } return handler, csrf } func TestConsoleOperatorsList(t *testing.T) { fake := &fakeOperatorAdmin{list: []admin.Admin{{Username: "root"}}} router, _ := operatorsRouter(t, fake) rec := consoleGet(t, router, "/_gm/operators") if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String()) } for _, want := range []string{"root", "Create operator", "Reset"} { if !strings.Contains(rec.Body.String(), want) { t.Errorf("operators page missing %q", want) } } } func TestConsoleOperatorCreate(t *testing.T) { fake := &fakeOperatorAdmin{} router, csrf := operatorsRouter(t, fake) rec := consolePost(t, router, "/_gm/operators", "_csrf="+csrf.Token("ops")+"&username=mod&password=s3cret") if rec.Code != http.StatusSeeOther { t.Fatalf("status = %d, want 303; body=%s", rec.Code, rec.Body.String()) } if fake.createCalls != 1 || fake.created.Username != "mod" || fake.created.Password != "s3cret" { t.Errorf("create recorded %d username=%q", fake.createCalls, fake.created.Username) } } func TestConsoleOperatorCreateConflict(t *testing.T) { fake := &fakeOperatorAdmin{createErr: admin.ErrUsernameTaken} router, csrf := operatorsRouter(t, fake) rec := consolePost(t, router, "/_gm/operators", "_csrf="+csrf.Token("ops")+"&username=root&password=x") if rec.Code != http.StatusConflict { t.Fatalf("status = %d, want 409", rec.Code) } } func TestConsoleOperatorDisableEnable(t *testing.T) { fake := &fakeOperatorAdmin{} router, csrf := operatorsRouter(t, fake) if rec := consolePost(t, router, "/_gm/operators/root/disable", "_csrf="+csrf.Token("ops")); rec.Code != http.StatusSeeOther { t.Fatalf("disable status = %d, want 303", rec.Code) } if rec := consolePost(t, router, "/_gm/operators/root/enable", "_csrf="+csrf.Token("ops")); rec.Code != http.StatusSeeOther { t.Fatalf("enable status = %d, want 303", rec.Code) } if fake.disableCalls != 1 || fake.enableCalls != 1 { t.Errorf("disable=%d enable=%d, want 1/1", fake.disableCalls, fake.enableCalls) } } func TestConsoleOperatorResetPassword(t *testing.T) { fake := &fakeOperatorAdmin{} router, csrf := operatorsRouter(t, fake) rec := consolePost(t, router, "/_gm/operators/root/reset-password", "_csrf="+csrf.Token("ops")+"&password=newpass") if rec.Code != http.StatusSeeOther { t.Fatalf("status = %d, want 303", rec.Code) } if fake.resetCalls != 1 || fake.lastResetUser != "root" || fake.lastResetPass != "newpass" { t.Errorf("reset recorded %d user=%q", fake.resetCalls, fake.lastResetUser) } } func TestConsoleOperatorResetPasswordMissing(t *testing.T) { fake := &fakeOperatorAdmin{} router, csrf := operatorsRouter(t, fake) rec := consolePost(t, router, "/_gm/operators/root/reset-password", "_csrf="+csrf.Token("ops")) if rec.Code != http.StatusBadRequest { t.Fatalf("status = %d, want 400", rec.Code) } if fake.resetCalls != 0 { t.Error("reset must not run without a password") } } func TestConsoleOperatorRejectsBadCSRF(t *testing.T) { fake := &fakeOperatorAdmin{} router, _ := operatorsRouter(t, fake) rec := consolePost(t, router, "/_gm/operators/root/disable", "") if rec.Code != http.StatusForbidden { t.Fatalf("status = %d, want 403", rec.Code) } if fake.disableCalls != 0 { t.Error("disable must not run without a CSRF token") } } func TestConsoleOperatorsUnavailable(t *testing.T) { router, _ := operatorsRouter(t, nil) rec := consoleGet(t, router, "/_gm/operators") if rec.Code != http.StatusServiceUnavailable { t.Fatalf("status = %d, want 503", rec.Code) } }