//go:build e2e // +build e2e package e2e import ( "io" "net/http" "os" "testing" "time" "github.com/golang-jwt/jwt/v5" "github.com/rede5/gohorsejobs/backend/internal/database" ) // TestE2E_Users_List tests listing users with RBAC func TestE2E_Users_List(t *testing.T) { client := newTestClient() companyID, userID := setupTestCompanyAndUser(t) // Creates a SuperAdmin user (roles: superadmin) defer cleanupTestCompanyAndUser(t, companyID, userID) // ===================== // 1. Unauthenticated Request // ===================== t.Run("Unauthenticated", func(t *testing.T) { client.setAuthToken("") // Clear token resp, err := client.get("/api/v1/users") if err != nil { t.Fatalf("Failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusUnauthorized { t.Errorf("Expected status 401, got %d", resp.StatusCode) } }) // ===================== // 2. Authenticated as SuperAdmin (Authorized) // ===================== t.Run("AsSuperAdmin", func(t *testing.T) { // createAuthToken creates a token with 'superadmin' role by default in jobs_e2e_test.go // We can reuse it or use our new helper. // Let's use our explicit helper to be sure. token := createTokenWithRoles(t, userID, companyID, []string{"superadmin"}) client.setAuthToken(token) resp, err := client.get("/api/v1/users") if err != nil { t.Fatalf("Failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) t.Errorf("Expected status 200, got %d. Body: %s", resp.StatusCode, string(body)) } }) // ===================== // 2a. Authenticated as Admin (Authorized) // ===================== t.Run("AsAdmin", func(t *testing.T) { token := createTokenWithRoles(t, userID, companyID, []string{"admin"}) client.setAuthToken(token) resp, err := client.get("/api/v1/users") if err != nil { t.Fatalf("Failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("Expected status 200, got %d", resp.StatusCode) } }) // ===================== // 3. Authenticated as Candidate (Forbidden) // ===================== t.Run("AsCandidate", func(t *testing.T) { // Create a candidate user candID := createTestCandidate(t) defer database.DB.Exec("DELETE FROM users WHERE id = $1", candID) token := createTokenWithRoles(t, candID, companyID, []string{"candidate"}) client.setAuthToken(token) resp, err := client.get("/api/v1/users") if err != nil { t.Fatalf("Failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusForbidden { t.Errorf("Expected status 403, got %d", resp.StatusCode) } }) // ===================== // 4. Authenticated as Recruiter (Forbidden - assuming Recruiters can't list all users) // ===================== t.Run("AsRecruiter", func(t *testing.T) { // Uses same user/company for simplicity, just different claim token := createTokenWithRoles(t, userID, companyID, []string{"recruiter"}) client.setAuthToken(token) resp, err := client.get("/api/v1/users") if err != nil { t.Fatalf("Failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusForbidden { t.Errorf("Expected status 403, got %d", resp.StatusCode) } }) } // Helper to create a candidate user func createTestCandidate(t *testing.T) string { var id string query := ` INSERT INTO users (identifier, password_hash, role, full_name, email, status, created_at, updated_at) VALUES ('e2e_candidate_' || gen_random_uuid(), 'hash', 'candidate', 'E2E Candidate', 'cand@e2e.com', 'active', NOW(), NOW()) RETURNING id ` err := database.DB.QueryRow(query).Scan(&id) if err != nil { t.Fatalf("Failed to create candidate: %v", err) } // Insert role database.DB.Exec("INSERT INTO user_roles (user_id, role) VALUES ($1, 'candidate')", id) return id } func createTokenWithRoles(t *testing.T, userID, tenantID string, roles []string) string { secret := os.Getenv("JWT_SECRET") if secret == "" { secret = "gohorse-super-secret-key-2024-production" } claims := jwt.MapClaims{ "sub": userID, "tenant": tenantID, "roles": roles, "iss": "gohorse-jobs", "exp": time.Now().Add(time.Hour).Unix(), "iat": time.Now().Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenStr, err := token.SignedString([]byte(secret)) if err != nil { t.Fatalf("Failed to generate token: %v", err) } return tokenStr }