From 3daad379e01640c304d7597c31cde6842af3c6e2 Mon Sep 17 00:00:00 2001 From: Huxley Date: Sat, 4 Jul 2026 17:23:35 +0800 Subject: [PATCH] test(server): add admin route integration test --- internal/server/server_test.go | 124 +++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 99ccc11..6012a11 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -1,6 +1,8 @@ package server import ( + "bytes" + "context" "encoding/json" "net/http" "net/http/httptest" @@ -8,7 +10,9 @@ import ( "time" "github.com/dhao2001/mygo/internal/app" + "github.com/dhao2001/mygo/internal/auth" "github.com/dhao2001/mygo/internal/config" + "github.com/dhao2001/mygo/internal/repository" "github.com/dhao2001/mygo/internal/service" ) @@ -56,3 +60,123 @@ func TestAddress(t *testing.T) { t.Errorf("Address() = %q, want %q", got, want) } } + +func TestAdminRoutes(t *testing.T) { + // Setup SQLite :memory: + dbCfg := config.DatabaseConfig{ + Driver: "sqlite3", + SQLite: config.SQLiteConfig{Path: ":memory:"}, + } + db, err := repository.Open(dbCfg) + if err != nil { + t.Fatalf("open db: %v", err) + } + if err := repository.AutoMigrate(db); err != nil { + t.Fatalf("migrate: %v", err) + } + + userRepo := repository.NewUserRepository(db) + sessionRepo := repository.NewSessionRepository(db) + credentialRepo := repository.NewCredentialRepository(db) + + jwtSecret := []byte("test-secret") + accessTTL := 15 * time.Minute + refreshTTL := 168 * time.Hour + + authService := service.NewAuthService(userRepo, sessionRepo, credentialRepo, jwtSecret, accessTTL, refreshTTL) + + cfg := &config.Config{ + JWT: config.JWTConfig{ + Secret: "test-secret", + AccessTTL: accessTTL, + RefreshTTL: refreshTTL, + }, + } + + webApp := app.NewWebApp(cfg, db, userRepo, sessionRepo, nil, credentialRepo, authService, nil, nil) + router := NewRouter(webApp) + + // Helper: register a user via POST /api/v1/auth/register and return the user ID. + register := func(username, email, password string) string { + body, _ := json.Marshal(map[string]string{ + "username": username, + "email": email, + "password": password, + }) + req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/register", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusCreated { + t.Fatalf("register %s: status=%d, body=%s", username, rec.Code, rec.Body.String()) + } + var user struct { + ID string `json:"id"` + } + if err := json.Unmarshal(rec.Body.Bytes(), &user); err != nil { + t.Fatalf("unmarshal register response: %v", err) + } + return user.ID + } + + // Register admin user and promote to admin. + adminID := register("admin", "admin@test.local", "adminpass1") + adminUser, err := userRepo.FindByID(context.Background(), adminID) + if err != nil { + t.Fatalf("find admin user: %v", err) + } + adminUser.IsAdmin = true + if err := userRepo.Update(context.Background(), adminUser); err != nil { + t.Fatalf("promote to admin: %v", err) + } + + // Generate admin JWT. + adminToken, err := auth.GenerateAccessToken(adminID, jwtSecret, accessTTL) + if err != nil { + t.Fatalf("generate admin token: %v", err) + } + + // Register a regular (non-admin) user. + regularID := register("regular", "regular@test.local", "regularpass1") + regularToken, err := auth.GenerateAccessToken(regularID, jwtSecret, accessTTL) + if err != nil { + t.Fatalf("generate regular token: %v", err) + } + + // Register a victim user to delete. + victimID := register("victim", "victim@test.local", "victimpass1") + + // Helper: make a DELETE request with optional auth header. + deleteUser := func(userID, token string) *httptest.ResponseRecorder { + url := "/api/v1/admin/users/" + userID + req := httptest.NewRequest(http.MethodDelete, url, nil) + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + return rec + } + + // Test 1: Admin can delete a user → 204. + rec := deleteUser(victimID, adminToken) + if rec.Code != http.StatusNoContent { + t.Errorf("admin delete: status=%d, want %d, body=%s", rec.Code, http.StatusNoContent, rec.Body.String()) + } + + // Register a second victim for the remaining tests (first victim is soft-deleted). + victim2ID := register("victim2", "victim2@test.local", "victim2pass1") + + // Test 2: Non-admin JWT → 403. + rec = deleteUser(victim2ID, regularToken) + if rec.Code != http.StatusForbidden { + t.Errorf("non-admin delete: status=%d, want %d, body=%s", rec.Code, http.StatusForbidden, rec.Body.String()) + } + + // Test 3: No auth → 401. + rec = deleteUser(victim2ID, "") + if rec.Code != http.StatusUnauthorized { + t.Errorf("no-auth delete: status=%d, want %d, body=%s", rec.Code, http.StatusUnauthorized, rec.Body.String()) + } +}