Files
Crussell/backend/handlers/today/today_test.go
T
popertotsandSisyphus 1cf7083bcc feat(backend): add today handler tests
Add comprehensive test suite for today package handlers.

- Add tests for current/next appointment, today appointments, and pending approvals
- Uses testutils.SetupTestDB for isolated test database
- Follows established test patterns in the codebase

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-20 16:59:14 +01:00

373 lines
11 KiB
Go

//go:build test
// +build test
package today
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"crussell/db"
"crussell/testutils"
"crussell/testutils/fixtures"
)
func createTodayService(t *testing.T) string {
t.Helper()
var svcID string
err := db.DB.QueryRow(context.Background(), `
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, bookingID, serviceID string) {
t.Helper()
_, err := db.DB.Exec(context.Background(), `
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) {
testutils.SetupTestDB(t)
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
var origFirstName, origLastName string
err = db.DB.QueryRow(context.Background(), `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 = db.DB.Exec(context.Background(), `
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)
var bookingID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, NOW() + INTERVAL '5 minutes', 'in_progress')
RETURNING id
`, userID).Scan(&bookingID)
if err != nil {
t.Fatalf("failed to create booking: %v", err)
}
addBookingService(t, bookingID, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/appointments", nil)
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) {
testutils.SetupTestDB(t)
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
svcID := createTodayService(t)
var bookingID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, NOW() + INTERVAL '5 minutes', 'in_progress')
RETURNING id
`, userID).Scan(&bookingID)
addBookingService(t, bookingID, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/appointments", nil)
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) {
testutils.SetupTestDB(t)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/appointments", nil)
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) {
testutils.SetupTestDB(t)
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
var origFirstName, origLastName string
err = db.DB.QueryRow(context.Background(), `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 = db.DB.Exec(context.Background(), `
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)
var bookingID string
err = db.DB.QueryRow(context.Background(), `
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, bookingID, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/pending-approvals", nil)
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) {
testutils.SetupTestDB(t)
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
svcID := createTodayService(t)
var bookingID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, NOW() + INTERVAL '1 day', 'pending')
RETURNING id
`, userID).Scan(&bookingID)
addBookingService(t, bookingID, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/pending-approvals", nil)
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) {
testutils.SetupTestDB(t)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/pending-approvals", nil)
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) {
testutils.SetupTestDB(t)
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
_, err = db.DB.Exec(context.Background(), `
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)
// Ensure working hours for all weekdays
for wd := 0; wd <= 6; wd++ {
db.DB.Exec(context.Background(), `
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 = db.DB.QueryRow(context.Background(), `
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, bookingID2, svcID)
req := httptest.NewRequest(http.MethodGet, "/api/admin/today/current-next", nil)
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)
}
}