package tests import ( "context" "encoding/json" "net/http" "testing" "github.com/gofiber/fiber/v2" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" "knowfoolery/backend/shared/auth" "knowfoolery/backend/shared/database" "knowfoolery/backend/services/game-service/internal/services" ) // TestEnvironment holds the test environment setup type TestEnvironment struct { DB *database.Client AuthService *auth.MockAuthService GameService *services.GameService App *fiber.App AdminToken string PlayerToken string ContainerCleanup func() } // SetupTestEnvironment creates a test environment with PostgreSQL container func SetupTestEnvironment(t *testing.T) *TestEnvironment { ctx := context.Background() // Try to start PostgreSQL container (fallback to SQLite if fails) var postgresContainer testcontainers.Container postgresContainer, err := postgres.RunContainer(ctx, testcontainers.WithImage("postgres:15-alpine"), postgres.WithDatabase("knowfoolery_test"), postgres.WithUsername("test_user"), postgres.WithPassword("test_password"), ) if err != nil { t.Logf("Failed to start postgres container, using SQLite: %v", err) } // Create database client dbConfig := database.Config{ Driver: "postgres", Host: "localhost", Port: 5432, // Will be overridden by connection string parsing User: "test_user", Password: "test_password", Database: "knowfoolery_test", SSLMode: "disable", } // For simplicity, create a simple connection (in real implementation, parse connStr) db, err := database.NewClient(dbConfig) if err != nil { // If postgres fails, fallback to SQLite for testing t.Logf("PostgreSQL connection failed, using SQLite: %v", err) dbConfig = database.Config{ Driver: "sqlite3", Database: ":memory:", } db, err = database.NewClient(dbConfig) if err != nil { t.Fatalf("Failed to create database client: %v", err) } } // Create tables if err := db.CreateTables(ctx); err != nil { t.Fatalf("Failed to create tables: %v", err) } // Initialize services authService, err := auth.NewMockAuthService("test-service") if err != nil { t.Fatalf("Failed to create auth service: %v", err) } gameService := services.NewGameService(db) // Create test tokens tokens := authService.CreateMockUsers() // Create Fiber app for testing app := fiber.New(fiber.Config{ DisableStartupMessage: true, }) // Add basic routes for testing app.Get("/health", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{"status": "healthy"}) }) // Cleanup function cleanup := func() { db.Close() if postgresContainer != nil { postgresContainer.Terminate(ctx) } } return &TestEnvironment{ DB: db, AuthService: authService, GameService: gameService, App: app, AdminToken: tokens["admin"], PlayerToken: tokens["player"], ContainerCleanup: cleanup, } } // TestHealthEndpoint tests the health check endpoint func TestHealthEndpoint(t *testing.T) { env := SetupTestEnvironment(t) defer env.ContainerCleanup() // Test health endpoint req, err := http.NewRequest("GET", "/health", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } resp, err := env.App.Test(req) if err != nil { t.Fatalf("Failed to test request: %v", err) } if resp.StatusCode != 200 { t.Errorf("Expected status 200, got %d", resp.StatusCode) } } // TestDatabaseConnection tests that we can connect to the database func TestDatabaseConnection(t *testing.T) { env := SetupTestEnvironment(t) defer env.ContainerCleanup() // Test database health ctx := context.Background() if err := env.DB.Health(ctx); err != nil { t.Errorf("Database health check failed: %v", err) } } // TestAuthTokenGeneration tests mock authentication token generation func TestAuthTokenGeneration(t *testing.T) { env := SetupTestEnvironment(t) defer env.ContainerCleanup() // Validate admin token ctx := context.Background() adminClaims, err := env.AuthService.ValidateToken(ctx, env.AdminToken) if err != nil { t.Errorf("Failed to validate admin token: %v", err) } if adminClaims.UserID != "admin-1" { t.Errorf("Expected admin user ID 'admin-1', got '%s'", adminClaims.UserID) } // Check admin has admin role hasAdminRole := false for _, role := range adminClaims.Roles { if role == "admin" { hasAdminRole = true break } } if !hasAdminRole { t.Error("Admin token should have admin role") } // Validate player token playerClaims, err := env.AuthService.ValidateToken(ctx, env.PlayerToken) if err != nil { t.Errorf("Failed to validate player token: %v", err) } if playerClaims.UserID != "player-1" { t.Errorf("Expected player user ID 'player-1', got '%s'", playerClaims.UserID) } } // TestGameSessionCreation tests creating a game session func TestGameSessionCreation(t *testing.T) { env := SetupTestEnvironment(t) defer env.ContainerCleanup() ctx := context.Background() userID := "test-user-1" // Create a game session session, err := env.GameService.CreateSession(ctx, "Test Player", &userID) if err != nil { t.Fatalf("Failed to create session: %v", err) } if session.PlayerName != "Test Player" { t.Errorf("Expected player name 'Test Player', got '%s'", session.PlayerName) } if session.UserID == nil || *session.UserID != userID { t.Errorf("Expected user ID '%s', got %v", userID, session.UserID) } if session.Status != "active" { t.Errorf("Expected status 'active', got '%s'", session.Status) } // Retrieve the session retrievedSession, err := env.GameService.GetSessionByID(ctx, session.ID) if err != nil { t.Fatalf("Failed to retrieve session: %v", err) } if retrievedSession.ID != session.ID { t.Errorf("Retrieved session ID doesn't match: expected '%s', got '%s'", session.ID, retrievedSession.ID) } } // TestRandomQuestionRetrieval tests retrieving random questions func TestRandomQuestionRetrieval(t *testing.T) { env := SetupTestEnvironment(t) defer env.ContainerCleanup() ctx := context.Background() // Note: This test will only work if we have sample data in the database // For now, it tests that the function doesn't crash question, err := env.GameService.GetRandomQuestion(ctx, "", "") // We expect this to fail since we haven't inserted test data yet if err == nil { t.Logf("Got random question: %s", question.Text) } else { t.Logf("No questions found (expected for empty database): %v", err) } } // TestAPIEndpointsWithAuth tests API endpoints with authentication func TestAPIEndpointsWithAuth(t *testing.T) { env := SetupTestEnvironment(t) defer env.ContainerCleanup() middleware := auth.NewJWTMiddleware(env.AuthService) // Add authenticated route api := env.App.Group("/api/v1") api.Use(middleware.Optional()) api.Get("/test", func(c *fiber.Ctx) error { user, err := auth.GetUserFromContext(c) if err != nil { return c.JSON(fiber.Map{"authenticated": false}) } return c.JSON(fiber.Map{ "authenticated": true, "user": user.Username, "roles": user.Roles, }) }) // Test without authentication req, err := http.NewRequest("GET", "/api/v1/test", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } resp, err := env.App.Test(req) if err != nil { t.Fatalf("Failed to test request: %v", err) } if resp.StatusCode != 200 { t.Errorf("Expected status 200, got %d", resp.StatusCode) } // Test with authentication authReq, err := http.NewRequest("GET", "/api/v1/test", nil) if err != nil { t.Fatalf("Failed to create authenticated request: %v", err) } authReq.Header.Set("Authorization", "Bearer "+env.PlayerToken) authResp, err := env.App.Test(authReq) if err != nil { t.Fatalf("Failed to test authenticated request: %v", err) } if authResp.StatusCode != 200 { t.Errorf("Expected status 200 for authenticated request, got %d", authResp.StatusCode) } } // Helper function to parse JSON response func parseJSONResponse(t *testing.T, resp *http.Response) map[string]interface{} { var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { t.Fatalf("Failed to parse JSON response: %v", err) } return result }