//go:build test // +build test package admin import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "crussell/db" "crussell/mw" "crussell/testutils/jwt" "crussell/testutils/testdb" "github.com/go-chi/chi/v5" ) // TestMain initializes test environment variables before any tests run func TestMain(m *testing.M) { // Set database environment variables for test database os.Setenv("POSTGRES_USER", "myuser") os.Setenv("POSTGRES_PASSWORD", "mypassword") os.Setenv("POSTGRES_HOST", "localhost") os.Setenv("POSTGRES_DB", "crussell_test") os.Setenv("GO_TESTING", "true") // Run the tests code := m.Run() os.Exit(code) } // setupTestDB replaces the global db.DB with a test pool and returns a cleanup function func setupTestDB(t *testing.T) func() { t.Helper() pool := testdb.Pool(t) testdb.Migrate(t, pool) testdb.TruncateTables(t, pool) // Clear data between tests originalDB := db.DB db.DB = pool jwt.Init() return func() { db.DB = originalDB pool.Close() } } // makeAdminRequest creates a request with admin context // Note: Using 12-char IDs to match CHAR(12) columns in schema (e.g., created_by) func makeAdminRequest(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder { return makeRequestWithContext(handler, method, path, body, "admin001", "admin") } // makeUserRequest creates a request with regular user context // Note: Using 12-char IDs to match CHAR(12) columns in schema (e.g., created_by) func makeUserRequest(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder { return makeRequestWithContext(handler, method, path, body, "user001", "verified_email") } // makeRequestWithContext creates a request with specific user context func makeRequestWithContext(handler http.Handler, method, path string, body interface{}, userID, role string) *httptest.ResponseRecorder { var req *http.Request if body != nil { bodyBytes, _ := json.Marshal(body) req = httptest.NewRequest(method, path, bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") } else { req = httptest.NewRequest(method, path, nil) } // Set up chi routing context (required for chi.URLParam to work) rctx := chi.NewRouteContext() // Parse the path to extract ID parameters for chi // chi routes like /api/admin/users/{id} need {id} in route context if method == "GET" || method == "PUT" || method == "POST" || method == "DELETE" || method == "PATCH" { // Extract path params from URL for chi if id, paramName := extractIDFromPath(path); id != "" { rctx.URLParams.Add(paramName, id) } } // Set up context with user ID and role (simulating middleware) ctx := context.WithValue(req.Context(), chi.RouteCtxKey, rctx) ctx = context.WithValue(ctx, mw.UserIDKey, userID) ctx = context.WithValue(ctx, mw.UserRoleKey, role) req = req.WithContext(ctx) w := httptest.NewRecorder() handler.ServeHTTP(w, req) return w } // extractIDFromPath extracts the ID from URL paths like /api/admin/users/{id} or /api/admin/bookings/{id}/progress // It returns only the ID segment, not any nested path parts func extractIDFromPath(path string) (string, string) { // Define patterns with their param names: (prefix, paramName) patterns := []struct { prefix string paramName string }{ {"/api/admin/bookings/user/", "user_id"}, {"/api/admin/users/", "id"}, {"/api/admin/bookings/", "id"}, {"/api/admin/services/", "id"}, {"/api/bookings/", "id"}, {"/api/services/eligible-for/", "userId"}, {"/api/services/", "id"}, } for _, p := range patterns { if idx := findLastSegment(path, p.prefix); idx >= 0 { // Extract only the ID segment (up to the next / or end of path) suffix := path[idx:] if slashIdx := findSlash(suffix); slashIdx >= 0 { return suffix[:slashIdx], p.paramName } return suffix, p.paramName } } return "", "" } // findSlash finds the position of the first / in the string func findSlash(s string) int { for i := 0; i < len(s); i++ { if s[i] == '/' { return i } } return -1 } func findLastSegment(path, prefix string) int { for i := len(path) - 1; i >= len(prefix); i-- { if len(path) > i && path[i-len(prefix):i] == prefix { return i } } return -1 } func parseResponseBody(w *httptest.ResponseRecorder, dest interface{}) error { return json.Unmarshal(w.Body.Bytes(), dest) }