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>
373 lines
11 KiB
Go
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)
|
|
}
|
|
}
|