//go:build e2e // +build e2e package e2e import ( "fmt" "net/http" "testing" "time" "github.com/rede5/gohorsejobs/backend/internal/database" "github.com/rede5/gohorsejobs/backend/internal/dto" "github.com/rede5/gohorsejobs/backend/internal/models" ) // setupTestJobForApplications creates a test company, user, and job for application tests func setupTestJobForApplications(t *testing.T) (companyID, userID, jobID string) { t.Helper() // Create user first userQuery := ` INSERT INTO users (identifier, password_hash, role, full_name, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (identifier) DO UPDATE SET password_hash = $2 RETURNING id ` err := database.DB.QueryRow( userQuery, "e2e-test-apps-user", "hashedpassword", "superadmin", "E2E Test Apps User", time.Now(), time.Now(), ).Scan(&userID) if err != nil { t.Fatalf("Failed to create test user: %v", err) } // Create company companyQuery := ` INSERT INTO companies (name, slug, type, active, verified, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (slug) DO UPDATE SET name = $1 RETURNING id ` err = database.DB.QueryRow( companyQuery, "E2E Test Company for Apps", "e2e-test-company-apps", "employer", true, false, time.Now(), time.Now(), ).Scan(&companyID) if err != nil { t.Fatalf("Failed to create test company: %v", err) } // Create job jobQuery := ` INSERT INTO jobs (company_id, created_by, title, description, status, visa_support, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id ` err = database.DB.QueryRow( jobQuery, companyID, userID, "E2E Test Job for Applications", "Test job to receive applications", "open", false, time.Now(), time.Now(), ).Scan(&jobID) if err != nil { t.Fatalf("Failed to create test job: %v", err) } return companyID, userID, jobID } // cleanupTestJobForApplications removes test data func cleanupTestJobForApplications(t *testing.T, companyID, userID, jobID string) { t.Helper() database.DB.Exec("DELETE FROM applications WHERE job_id = $1", jobID) database.DB.Exec("DELETE FROM jobs WHERE id = $1", jobID) database.DB.Exec("DELETE FROM companies WHERE id = $1", companyID) } // TestE2E_Applications_CRUD tests the complete application flow func TestE2E_Applications_CRUD(t *testing.T) { client := newTestClient() companyID, userID, jobID := setupTestJobForApplications(t) defer cleanupTestJobForApplications(t, companyID, userID, jobID) var createdAppID string // ===================== // 1. CREATE APPLICATION // ===================== t.Run("CreateApplication", func(t *testing.T) { name := "John Doe E2E Test" email := "john.e2e@test.com" phone := "+81-90-1234-5678" message := "I am interested in this position. This is an E2E test application." appReq := dto.CreateApplicationRequest{ JobID: jobID, Name: &name, Email: &email, Phone: &phone, Message: &message, } resp, err := client.post("/api/v1/applications", appReq) if err != nil { t.Fatalf("Failed to create application: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { t.Errorf("Expected status 201, got %d", resp.StatusCode) } var app models.Application if err := parseJSON(resp, &app); err != nil { t.Fatalf("Failed to parse response: %v", err) } if app.Status != "pending" { t.Errorf("Expected status 'pending', got '%s'", app.Status) } if app.JobID != jobID { t.Errorf("Expected jobID %s, got %s", jobID, app.JobID) } createdAppID = app.ID t.Logf("Created application with ID: %s", createdAppID) }) // ===================== // 2. GET APPLICATION BY ID // ===================== t.Run("GetApplicationByID", func(t *testing.T) { if createdAppID == "" { t.Skip("No application was created") } resp, err := client.get(fmt.Sprintf("/api/v1/applications/%s", createdAppID)) if err != nil { t.Fatalf("Failed to get application: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("Expected status 200, got %d", resp.StatusCode) } var app models.Application if err := parseJSON(resp, &app); err != nil { t.Fatalf("Failed to parse response: %v", err) } if app.ID != createdAppID { t.Errorf("Expected application ID %s, got %s", createdAppID, app.ID) } }) // ===================== // 3. LIST APPLICATIONS BY JOB // ===================== t.Run("ListApplicationsByJob", func(t *testing.T) { resp, err := client.get(fmt.Sprintf("/api/v1/applications?jobId=%s", jobID)) if err != nil { t.Fatalf("Failed to list applications: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("Expected status 200, got %d", resp.StatusCode) } var apps []models.Application if err := parseJSON(resp, &apps); err != nil { t.Fatalf("Failed to parse response: %v", err) } if len(apps) < 1 { t.Error("Expected at least 1 application") } }) // ===================== // 4. UPDATE APPLICATION STATUS // ===================== t.Run("UpdateStatusToReviewed", func(t *testing.T) { if createdAppID == "" { t.Skip("No application was created") } statusReq := dto.UpdateApplicationStatusRequest{ Status: "reviewed", } resp, err := client.put(fmt.Sprintf("/api/v1/applications/%s/status", createdAppID), statusReq) if err != nil { t.Fatalf("Failed to update status: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("Expected status 200, got %d", resp.StatusCode) } var app models.Application if err := parseJSON(resp, &app); err != nil { t.Fatalf("Failed to parse response: %v", err) } if app.Status != "reviewed" { t.Errorf("Expected status 'reviewed', got '%s'", app.Status) } }) // ===================== // 5. UPDATE TO HIRED // ===================== t.Run("UpdateStatusToHired", func(t *testing.T) { if createdAppID == "" { t.Skip("No application was created") } statusReq := dto.UpdateApplicationStatusRequest{ Status: "hired", } resp, err := client.put(fmt.Sprintf("/api/v1/applications/%s/status", createdAppID), statusReq) if err != nil { t.Fatalf("Failed to update status: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("Expected status 200, got %d", resp.StatusCode) } var app models.Application if err := parseJSON(resp, &app); err != nil { t.Fatalf("Failed to parse response: %v", err) } if app.Status != "hired" { t.Errorf("Expected status 'hired', got '%s'", app.Status) } }) } // TestE2E_Applications_MultipleApplicants tests multiple applications for the same job func TestE2E_Applications_MultipleApplicants(t *testing.T) { client := newTestClient() companyID, userID, jobID := setupTestJobForApplications(t) defer cleanupTestJobForApplications(t, companyID, userID, jobID) // Create 3 applications applicants := []string{"Alice", "Bob", "Charlie"} for _, name := range applicants { appName := name + " E2E Test" email := name + "@e2etest.com" appReq := dto.CreateApplicationRequest{ JobID: jobID, Name: &appName, Email: &email, } resp, _ := client.post("/api/v1/applications", appReq) resp.Body.Close() } // List all applications for job resp, err := client.get(fmt.Sprintf("/api/v1/applications?jobId=%s", jobID)) if err != nil { t.Fatalf("Failed to list applications: %v", err) } defer resp.Body.Close() var apps []models.Application if err := parseJSON(resp, &apps); err != nil { t.Fatalf("Failed to parse response: %v", err) } if len(apps) != 3 { t.Errorf("Expected 3 applications, got %d", len(apps)) } } // TestE2E_Applications_Errors tests error handling func TestE2E_Applications_Errors(t *testing.T) { client := newTestClient() t.Run("GetNonExistentApplication", func(t *testing.T) { resp, err := client.get("/api/v1/applications/999999") if err != nil { t.Fatalf("Failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusNotFound { t.Errorf("Expected status 404, got %d", resp.StatusCode) } }) t.Run("ListApplicationsMissingJobId", func(t *testing.T) { resp, err := client.get("/api/v1/applications") if err != nil { t.Fatalf("Failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Errorf("Expected status 400, got %d", resp.StatusCode) } }) }