mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
The reminder pipeline fired regardless of what happened to the booking after creation: cancelled customers got reminded about bookings that weren't happening, reschedules kept the old reminder time, and waitlisted bookings armed jobs too. Pending jobs are now cancelled on both cancel paths, deleted and re-armed on reschedule, and dispatch joins bookings.status to skip anything not confirmed. Reschedule also gained the same availability-window and blocked-day validation as booking creation, closing a 3am-reschedule hole via the manage URL. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
141 lines
4.4 KiB
Go
141 lines
4.4 KiB
Go
package notifications
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"bookra/apps/backend/internal/config"
|
|
"bookra/apps/backend/internal/db"
|
|
"bookra/apps/backend/internal/domain"
|
|
)
|
|
|
|
func TestDispatchDueProcessesPendingEmailReminders(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
startsAt := time.Now().UTC().Add(26 * time.Hour)
|
|
created, err := repo.CreateBooking(context.Background(), db.CreateBookingParams{
|
|
TenantID: "5d6b3551-0a3e-4b86-bdf0-e9df20a47148",
|
|
BookingMode: "appointment",
|
|
CustomerName: "Reminder Customer",
|
|
CustomerEmail: "[email protected]",
|
|
StartsAt: startsAt,
|
|
EndsAt: startsAt.Add(time.Hour),
|
|
Status: "confirmed",
|
|
Reference: "BK-TEST-REMINDER",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create booking: %v", err)
|
|
}
|
|
if err := repo.CreateReminderJob(context.Background(), db.ReminderJobParams{
|
|
TenantID: "5d6b3551-0a3e-4b86-bdf0-e9df20a47148",
|
|
BookingID: created.ID,
|
|
Channel: "email",
|
|
ScheduledFor: time.Now().UTC().Add(-time.Minute),
|
|
}); err != nil {
|
|
t.Fatalf("create reminder job: %v", err)
|
|
}
|
|
|
|
service := NewService(config.Config{
|
|
Environment: "development",
|
|
EmailFrom: "[email protected]",
|
|
}, repo)
|
|
|
|
response, err := service.DispatchDue(context.Background(), 10)
|
|
if err != nil {
|
|
t.Fatalf("dispatch due: %v", err)
|
|
}
|
|
if response.ProcessedCount != 1 {
|
|
t.Fatalf("expected processed count 1, got %d", response.ProcessedCount)
|
|
}
|
|
if response.SentCount != 1 {
|
|
t.Fatalf("expected sent count 1, got %d", response.SentCount)
|
|
}
|
|
if response.FailedCount != 0 {
|
|
t.Fatalf("expected failed count 0, got %d", response.FailedCount)
|
|
}
|
|
|
|
pending, err := repo.ListDueReminderJobs(context.Background(), time.Now().UTC().Add(time.Hour), 10)
|
|
if err != nil {
|
|
t.Fatalf("list due reminder jobs: %v", err)
|
|
}
|
|
if len(pending) != 0 {
|
|
t.Fatalf("expected no pending jobs after dispatch, got %d", len(pending))
|
|
}
|
|
}
|
|
|
|
func TestDispatchDueFailsUnknownChannel(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
startsAt := time.Now().UTC().Add(26 * time.Hour)
|
|
created, err := repo.CreateBooking(context.Background(), db.CreateBookingParams{
|
|
TenantID: "5d6b3551-0a3e-4b86-bdf0-e9df20a47148",
|
|
BookingMode: "appointment",
|
|
StartsAt: startsAt,
|
|
EndsAt: startsAt.Add(time.Hour),
|
|
Status: "confirmed",
|
|
Reference: "BK-TEST-PUSH",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create booking: %v", err)
|
|
}
|
|
if err := repo.CreateReminderJob(context.Background(), db.ReminderJobParams{
|
|
TenantID: "5d6b3551-0a3e-4b86-bdf0-e9df20a47148",
|
|
BookingID: created.ID,
|
|
Channel: "push",
|
|
ScheduledFor: time.Now().UTC().Add(-time.Minute),
|
|
}); err != nil {
|
|
t.Fatalf("create reminder job: %v", err)
|
|
}
|
|
|
|
service := NewService(config.Config{Environment: "development"}, repo)
|
|
response, err := service.DispatchDue(context.Background(), 10)
|
|
if err != nil {
|
|
t.Fatalf("dispatch due: %v", err)
|
|
}
|
|
if response.ProcessedCount != 1 || response.FailedCount != 1 {
|
|
t.Fatalf("expected one failed job, got %+v", response)
|
|
}
|
|
}
|
|
|
|
func TestDispatchRequestContractShape(t *testing.T) {
|
|
request := domain.DispatchReminderJobsRequest{Limit: 20}
|
|
if request.Limit != 20 {
|
|
t.Fatalf("expected limit 20, got %d", request.Limit)
|
|
}
|
|
}
|
|
|
|
func TestDispatchDueSkipsNonConfirmedBooking(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
startsAt := time.Now().UTC().Add(26 * time.Hour)
|
|
created, err := repo.CreateBooking(context.Background(), db.CreateBookingParams{
|
|
TenantID: "5d6b3551-0a3e-4b86-bdf0-e9df20a47148",
|
|
BookingMode: "appointment",
|
|
StartsAt: startsAt,
|
|
EndsAt: startsAt.Add(time.Hour),
|
|
Status: "confirmed",
|
|
Reference: "BK-TEST-SKIP",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create booking: %v", err)
|
|
}
|
|
if err := repo.CreateReminderJob(context.Background(), db.ReminderJobParams{
|
|
TenantID: "5d6b3551-0a3e-4b86-bdf0-e9df20a47148",
|
|
BookingID: created.ID,
|
|
Channel: "email",
|
|
ScheduledFor: time.Now().UTC().Add(-time.Minute),
|
|
}); err != nil {
|
|
t.Fatalf("create reminder job: %v", err)
|
|
}
|
|
if err := repo.UpdateBookingStatus(context.Background(), created.ID, "cancelled"); err != nil {
|
|
t.Fatalf("cancel booking: %v", err)
|
|
}
|
|
|
|
service := NewService(config.Config{Environment: "development"}, repo)
|
|
response, err := service.DispatchDue(context.Background(), 10)
|
|
if err != nil {
|
|
t.Fatalf("dispatch due: %v", err)
|
|
}
|
|
if response.ProcessedCount != 1 || response.SkippedCount != 1 || response.SentCount != 0 {
|
|
t.Fatalf("expected 1 skipped, got %+v", response)
|
|
}
|
|
}
|