diff --git a/backend/handlers/today/today_test.go b/backend/handlers/today/today_test.go new file mode 100644 index 0000000..09e7008 --- /dev/null +++ b/backend/handlers/today/today_test.go @@ -0,0 +1,372 @@ +//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) + } +}