Files
Crussell/backend/handlers/today/today_test.go
T
popertotsandSisyphus 220a0ef6e8 refactor(backend): update test files for PoolProxy and per-test transactions
Migrate all test files from SetupTestDB/db.DB pattern to per-test transactions:

- Replace SetupTestDB(t) with SetupTestTx(t) for context + transaction
- Replace db.DB.Query/QueryRow/Exec with tx.Query/QueryRow/Exec
- Replace context.Background() with context from SetupTestTx
- Replace defer rows.Close() pattern with explicit rows.Close()
- Add testdb.SeedBaseline(pool) to all TestMain functions
- Wire db.Conn = db.NewPoolProxy(pool) in all TestMain functions

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-21 19:29:24 +01:00

390 lines
11 KiB
Go

//go:build test
// +build test
package today
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"crussell/db"
"crussell/testutils"
"crussell/testutils/fixtures"
)
func createTodayService(t *testing.T, ctx context.Context, q db.Querier) string {
t.Helper()
var svcID string
err := q.QueryRow(ctx, `
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
VALUES ('Test Service', 'Description', 50.00, 60, true, 16)
RETURNING id
`).Scan(&svcID)
if err != nil {
t.Fatalf("failed to create service: %v", err)
}
return svcID
}
func addBookingService(t *testing.T, ctx context.Context, q db.Querier, bookingID, serviceID string) {
t.Helper()
_, err := q.Exec(ctx, `
INSERT INTO booking_services (booking_id, service_id)
VALUES ($1, $2)
`, bookingID, serviceID)
if err != nil {
t.Fatalf("failed to add booking service: %v", err)
}
}
func TestGetTodayAppointments_ShowsPreviousNameInAppointment(t *testing.T) {
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
var origFirstName, origLastName string
err = tx.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
if err != nil {
t.Fatalf("failed to query user name: %v", err)
}
_, err = tx.Exec(ctx, `
INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
VALUES ($1, 'OldFirst', 'OldLast')
`, userID)
if err != nil {
t.Fatalf("failed to insert name_history: %v", err)
}
svcID := createTodayService(t, ctx, tx)
var bookingID string
now := time.Now()
bookingStart := time.Date(now.Year(), now.Month(), now.Day(), 10, 0, 0, 0, now.Location())
err = tx.QueryRow(ctx, `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, $2, 'in_progress')
RETURNING id
`, userID, bookingStart).Scan(&bookingID)
if err != nil {
t.Fatalf("failed to create booking: %v", err)
}
addBookingService(t, ctx, tx, bookingID, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/appointments", nil)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
GetTodayAppointmentsHandler(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
}
var resp TodayAppointmentsResponse
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}
if len(resp.Appointments) == 0 {
t.Fatal("expected at least 1 appointment")
}
found := false
for _, a := range resp.Appointments {
if a.UserID == userID {
found = true
if a.PreviousFirstName == nil || *a.PreviousFirstName != "OldFirst" {
t.Errorf("expected previousFirstName 'OldFirst', got %v", a.PreviousFirstName)
}
if a.PreviousLastName == nil || *a.PreviousLastName != "OldLast" {
t.Errorf("expected previousLastName 'OldLast', got %v", a.PreviousLastName)
}
}
}
if !found {
t.Error("expected appointment for test user not found in response")
}
}
func TestGetTodayAppointments_OmitsPreviousNameWhenNoHistory(t *testing.T) {
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
svcID := createTodayService(t, ctx, tx)
var bookingID string
err = tx.QueryRow(ctx, `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, NOW() + INTERVAL '5 minutes', 'in_progress')
RETURNING id
`, userID).Scan(&bookingID)
addBookingService(t, ctx, tx, bookingID, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/appointments", nil)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
GetTodayAppointmentsHandler(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
}
var resp TodayAppointmentsResponse
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}
for _, a := range resp.Appointments {
if a.UserID == userID {
if a.PreviousFirstName != nil {
t.Errorf("expected previousFirstName nil (no history), got %v", *a.PreviousFirstName)
}
if a.PreviousLastName != nil {
t.Errorf("expected previousLastName nil (no history), got %v", *a.PreviousLastName)
}
}
}
}
func TestGetTodayAppointments_Empty(t *testing.T) {
t.Parallel()
ctx, _ := testutils.SetupTestTx(t)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/appointments", nil)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
GetTodayAppointmentsHandler(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
}
var resp TodayAppointmentsResponse
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}
if resp.Appointments == nil {
t.Error("expected empty array, got nil")
}
if len(resp.Appointments) != 0 {
t.Errorf("expected 0 appointments, got %d", len(resp.Appointments))
}
}
func TestGetPendingApprovals_ShowsPreviousName(t *testing.T) {
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
var origFirstName, origLastName string
err = tx.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
if err != nil {
t.Fatalf("failed to query user name: %v", err)
}
_, err = tx.Exec(ctx, `
INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
VALUES ($1, 'OldFirst', 'OldLast')
`, userID)
if err != nil {
t.Fatalf("failed to insert name_history: %v", err)
}
svcID := createTodayService(t, ctx, tx)
var bookingID string
err = tx.QueryRow(ctx, `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, NOW() + INTERVAL '1 day', 'pending')
RETURNING id
`, userID).Scan(&bookingID)
if err != nil {
t.Fatalf("failed to create booking: %v", err)
}
addBookingService(t, ctx, tx, bookingID, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/pending-approvals", nil)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
GetPendingApprovalsHandler(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
}
var resp PendingApprovalsResponse
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}
if len(resp.Approvals) == 0 {
t.Fatal("expected at least 1 pending approval")
}
found := false
for _, a := range resp.Approvals {
if a.UserID == userID {
found = true
if a.PreviousFirstName == nil || *a.PreviousFirstName != "OldFirst" {
t.Errorf("expected previousFirstName 'OldFirst', got %v", a.PreviousFirstName)
}
if a.PreviousLastName == nil || *a.PreviousLastName != "OldLast" {
t.Errorf("expected previousLastName 'OldLast', got %v", a.PreviousLastName)
}
}
}
if !found {
t.Error("expected pending approval for test user not found")
}
}
func TestGetPendingApprovals_OmitsPreviousNameWhenNoHistory(t *testing.T) {
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
svcID := createTodayService(t, ctx, tx)
var bookingID string
err = tx.QueryRow(ctx, `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, NOW() + INTERVAL '1 day', 'pending')
RETURNING id
`, userID).Scan(&bookingID)
addBookingService(t, ctx, tx, bookingID, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/pending-approvals", nil)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
GetPendingApprovalsHandler(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
}
var resp PendingApprovalsResponse
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}
for _, a := range resp.Approvals {
if a.UserID == userID {
if a.PreviousFirstName != nil {
t.Errorf("expected previousFirstName nil (no history), got %v", *a.PreviousFirstName)
}
if a.PreviousLastName != nil {
t.Errorf("expected previousLastName nil (no history), got %v", *a.PreviousLastName)
}
}
}
}
func TestGetPendingApprovals_Empty(t *testing.T) {
t.Parallel()
ctx, _ := testutils.SetupTestTx(t)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/pending-approvals", nil)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
GetPendingApprovalsHandler(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
}
var resp PendingApprovalsResponse
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}
if resp.Approvals == nil {
t.Error("expected empty array, got nil")
}
}
func TestGetCurrentNext_ShowsPreviousNameInAppointment(t *testing.T) {
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
_, err = tx.Exec(ctx, `
INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
VALUES ($1, 'OldFirst', 'OldLast')
`, userID)
if err != nil {
t.Fatalf("failed to insert name_history: %v", err)
}
svcID := createTodayService(t, ctx, tx)
// Ensure working hours for all weekdays
for wd := 0; wd <= 6; wd++ {
tx.Exec(ctx, `
INSERT INTO working_hours (weekday, start_time, end_time, is_open)
VALUES ($1, '09:00', '17:00', true)
ON CONFLICT (weekday) DO UPDATE SET start_time = '09:00', end_time = '17:00', is_open = true
`, wd)
}
var bookingID2 string
err = tx.QueryRow(ctx, `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, NOW(), 'confirmed')
RETURNING id
`, userID).Scan(&bookingID2)
if err != nil {
t.Fatalf("failed to create booking: %v", err)
}
addBookingService(t, ctx, tx, bookingID2, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/current-next", nil)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
GetCurrentAndNextHandler(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
}
var resp CurrentNextResponse
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}
if resp.Current == nil {
t.Fatal("expected current appointment, got nil")
}
if resp.Current.User == nil {
t.Fatal("expected user info on current appointment")
}
if resp.Current.User.PreviousFirstName == nil || *resp.Current.User.PreviousFirstName != "OldFirst" {
t.Errorf("expected previousFirstName 'OldFirst', got %v", resp.Current.User.PreviousFirstName)
}
if resp.Current.User.PreviousLastName == nil || *resp.Current.User.PreviousLastName != "OldLast" {
t.Errorf("expected previousLastName 'OldLast', got %v", resp.Current.User.PreviousLastName)
}
}