//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) } }