gohorsejobs/backend/tests/e2e/jobs_e2e_test.go

341 lines
8.9 KiB
Go

//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"
)
// setupTestCompanyAndUser creates a test company and user in the database and returns their IDs
func setupTestCompanyAndUser(t *testing.T) (companyID, userID int) {
t.Helper()
// Create user first (required for created_by in jobs)
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-jobs-user",
"hashedpassword",
"superadmin",
"E2E Test Jobs 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",
"e2e-test-company",
"employer",
true,
false,
time.Now(),
time.Now(),
).Scan(&companyID)
if err != nil {
t.Fatalf("Failed to create test company: %v", err)
}
return companyID, userID
}
// cleanupTestCompanyAndUser removes the test company and user
func cleanupTestCompanyAndUser(t *testing.T, companyID, userID int) {
t.Helper()
database.DB.Exec("DELETE FROM applications WHERE job_id IN (SELECT id FROM jobs WHERE company_id = $1)", companyID)
database.DB.Exec("DELETE FROM jobs WHERE company_id = $1", companyID)
database.DB.Exec("DELETE FROM companies WHERE id = $1", companyID)
// Don't delete user as it might be used elsewhere
}
// createTestJob creates a job directly in the database (bypasses API auth requirement)
func createTestJob(t *testing.T, companyID, userID int, title string) int {
t.Helper()
query := `
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
`
var jobID int
err := database.DB.QueryRow(
query,
companyID,
userID,
title,
"Test job created by E2E tests",
"open",
false,
time.Now(),
time.Now(),
).Scan(&jobID)
if err != nil {
t.Fatalf("Failed to create test job: %v", err)
}
return jobID
}
// TestE2E_Jobs_Read tests job reading operations
func TestE2E_Jobs_Read(t *testing.T) {
client := newTestClient()
companyID, userID := setupTestCompanyAndUser(t)
defer cleanupTestCompanyAndUser(t, companyID, userID)
// Create a test job directly in DB
jobID := createTestJob(t, companyID, userID, "E2E Test Software Engineer")
defer database.DB.Exec("DELETE FROM jobs WHERE id = $1", jobID)
// =====================
// 1. GET JOB BY ID
// =====================
t.Run("GetJobByID", func(t *testing.T) {
resp, err := client.get(fmt.Sprintf("/jobs/%d", jobID))
if err != nil {
t.Fatalf("Failed to get job: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
var job models.Job
if err := parseJSON(resp, &job); err != nil {
t.Fatalf("Failed to parse response: %v", err)
}
if job.ID != jobID {
t.Errorf("Expected job ID %d, got %d", jobID, job.ID)
}
if job.Title != "E2E Test Software Engineer" {
t.Errorf("Expected title 'E2E Test Software Engineer', got '%s'", job.Title)
}
})
// =====================
// 2. LIST JOBS
// =====================
t.Run("ListJobs", func(t *testing.T) {
resp, err := client.get("/jobs")
if err != nil {
t.Fatalf("Failed to list jobs: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
var response dto.PaginatedResponse
if err := parseJSON(resp, &response); err != nil {
t.Fatalf("Failed to parse response: %v", err)
}
// Should have at least our created job
if response.Pagination.Total < 1 {
t.Error("Expected at least 1 job in list")
}
})
}
// TestE2E_Jobs_Update tests job update operations
func TestE2E_Jobs_Update(t *testing.T) {
client := newTestClient()
companyID, userID := setupTestCompanyAndUser(t)
defer cleanupTestCompanyAndUser(t, companyID, userID)
// Create a test job
jobID := createTestJob(t, companyID, userID, "E2E Test Job for Update")
defer database.DB.Exec("DELETE FROM jobs WHERE id = $1", jobID)
t.Run("UpdateJobTitle", func(t *testing.T) {
newTitle := "E2E Test Updated Title"
updateReq := dto.UpdateJobRequest{
Title: &newTitle,
}
resp, err := client.put(fmt.Sprintf("/jobs/%d", jobID), updateReq)
if err != nil {
t.Fatalf("Failed to update job: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
var job models.Job
if err := parseJSON(resp, &job); err != nil {
t.Fatalf("Failed to parse response: %v", err)
}
if job.Title != newTitle {
t.Errorf("Expected title '%s', got '%s'", newTitle, job.Title)
}
})
t.Run("UpdateJobStatus", func(t *testing.T) {
newStatus := "closed"
updateReq := dto.UpdateJobRequest{
Status: &newStatus,
}
resp, err := client.put(fmt.Sprintf("/jobs/%d", jobID), updateReq)
if err != nil {
t.Fatalf("Failed to update job: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
})
}
// TestE2E_Jobs_Delete tests job deletion
func TestE2E_Jobs_Delete(t *testing.T) {
client := newTestClient()
companyID, userID := setupTestCompanyAndUser(t)
defer cleanupTestCompanyAndUser(t, companyID, userID)
// Create a test job to delete
jobID := createTestJob(t, companyID, userID, "E2E Test Job to Delete")
t.Run("DeleteJob", func(t *testing.T) {
resp, err := client.delete(fmt.Sprintf("/jobs/%d", jobID))
if err != nil {
t.Fatalf("Failed to delete job: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
t.Errorf("Expected status 204, got %d", resp.StatusCode)
}
// Verify job is deleted
verifyResp, _ := client.get(fmt.Sprintf("/jobs/%d", jobID))
if verifyResp.StatusCode != http.StatusNotFound {
t.Error("Job should be deleted but still exists")
}
verifyResp.Body.Close()
})
}
// TestE2E_Jobs_Filters tests job listing with filters
func TestE2E_Jobs_Filters(t *testing.T) {
client := newTestClient()
companyID, userID := setupTestCompanyAndUser(t)
defer cleanupTestCompanyAndUser(t, companyID, userID)
// Create multiple jobs directly in DB
for i := 1; i <= 3; i++ {
createTestJob(t, companyID, userID, fmt.Sprintf("E2E Test Job %d", i))
}
t.Run("Pagination", func(t *testing.T) {
resp, err := client.get("/jobs?page=1&limit=2")
if err != nil {
t.Fatalf("Failed to list jobs: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
})
t.Run("FilterByCompany", func(t *testing.T) {
resp, err := client.get(fmt.Sprintf("/jobs?companyId=%d", companyID))
if err != nil {
t.Fatalf("Failed to list jobs: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
var response dto.PaginatedResponse
if err := parseJSON(resp, &response); err != nil {
t.Fatalf("Failed to parse response: %v", err)
}
// Should have our 3 test jobs
if response.Pagination.Total < 3 {
t.Errorf("Expected at least 3 jobs for company, got %d", response.Pagination.Total)
}
})
}
// TestE2E_Jobs_InvalidInput tests error handling for invalid input
func TestE2E_Jobs_InvalidInput(t *testing.T) {
client := newTestClient()
t.Run("GetNonExistentJob", func(t *testing.T) {
resp, err := client.get("/jobs/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("GetInvalidJobID", func(t *testing.T) {
resp, err := client.get("/jobs/invalid")
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)
}
})
t.Run("CreateJobInvalidJSON", func(t *testing.T) {
req, _ := http.NewRequest("POST", testServer.URL+"/jobs", nil)
req.Header.Set("Content-Type", "application/json")
req.Body = http.NoBody
resp, err := http.DefaultClient.Do(req)
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)
}
})
}