341 lines
8.9 KiB
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)
|
|
}
|
|
})
|
|
}
|