mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
229 lines
6.5 KiB
Go
229 lines
6.5 KiB
Go
package bookings
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"bookra/apps/backend/internal/db"
|
|
"bookra/apps/backend/internal/domain"
|
|
)
|
|
|
|
func TestCreateAppointmentRejectsConflict(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
service := NewService(repo, nil)
|
|
|
|
availability, err := service.Availability(context.Background(), "studio-atelier")
|
|
if err != nil {
|
|
t.Fatalf("availability: %v", err)
|
|
}
|
|
|
|
var appointment domain.TimeSlot
|
|
for _, slot := range availability.Slots {
|
|
if slot.Mode == "appointment" {
|
|
appointment = slot
|
|
break
|
|
}
|
|
}
|
|
if appointment.StartsAt == "" {
|
|
t.Fatal("expected appointment slot")
|
|
}
|
|
|
|
first, err := service.Create(context.Background(), domain.CreateBookingRequest{
|
|
TenantSlug: "studio-atelier",
|
|
BookingMode: "appointment",
|
|
ServiceID: appointment.ServiceID,
|
|
StaffID: appointment.StaffID,
|
|
LocationID: appointment.LocationID,
|
|
CustomerName: "First",
|
|
CustomerEmail: "first@example.com",
|
|
StartsAt: appointment.StartsAt,
|
|
EndsAt: appointment.EndsAt,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("first create: %v", err)
|
|
}
|
|
if first.Status != "confirmed" {
|
|
t.Fatalf("expected confirmed, got %s", first.Status)
|
|
}
|
|
|
|
_, err = service.Create(context.Background(), domain.CreateBookingRequest{
|
|
TenantSlug: "studio-atelier",
|
|
BookingMode: "appointment",
|
|
ServiceID: appointment.ServiceID,
|
|
StaffID: appointment.StaffID,
|
|
LocationID: appointment.LocationID,
|
|
CustomerName: "Second",
|
|
CustomerEmail: "second@example.com",
|
|
StartsAt: appointment.StartsAt,
|
|
EndsAt: appointment.EndsAt,
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected conflict error")
|
|
}
|
|
if err != ErrBookingConflict {
|
|
t.Fatalf("expected ErrBookingConflict, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateClassFallsBackToWaitlistWhenCapacityReached(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
service := NewService(repo, nil)
|
|
|
|
availability, err := service.Availability(context.Background(), "studio-atelier")
|
|
if err != nil {
|
|
t.Fatalf("availability: %v", err)
|
|
}
|
|
|
|
var classSlot domain.TimeSlot
|
|
for _, slot := range availability.Slots {
|
|
if slot.Mode == "class" {
|
|
classSlot = slot
|
|
break
|
|
}
|
|
}
|
|
if classSlot.ClassSessionID == nil {
|
|
t.Fatal("expected class slot")
|
|
}
|
|
|
|
for i := 0; i < 4; i++ {
|
|
response, err := service.Create(context.Background(), domain.CreateBookingRequest{
|
|
TenantSlug: "studio-atelier",
|
|
BookingMode: "class",
|
|
ClassSessionID: classSlot.ClassSessionID,
|
|
LocationID: classSlot.LocationID,
|
|
CustomerName: "Capacity",
|
|
CustomerEmail: "capacity@example.com",
|
|
StartsAt: classSlot.StartsAt,
|
|
EndsAt: classSlot.EndsAt,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create within capacity: %v", err)
|
|
}
|
|
if response.Status != "confirmed" {
|
|
t.Fatalf("expected confirmed within capacity, got %s", response.Status)
|
|
}
|
|
}
|
|
|
|
response, err := service.Create(context.Background(), domain.CreateBookingRequest{
|
|
TenantSlug: "studio-atelier",
|
|
BookingMode: "class",
|
|
ClassSessionID: classSlot.ClassSessionID,
|
|
LocationID: classSlot.LocationID,
|
|
CustomerName: "Waitlist",
|
|
CustomerEmail: "waitlist@example.com",
|
|
StartsAt: classSlot.StartsAt,
|
|
EndsAt: classSlot.EndsAt,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create waitlist: %v", err)
|
|
}
|
|
if response.Status != "waitlisted" {
|
|
t.Fatalf("expected waitlisted, got %s", response.Status)
|
|
}
|
|
}
|
|
|
|
func TestCreateAppointmentRequiresTenantService(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
service := NewService(repo, nil)
|
|
|
|
_, err := service.Create(context.Background(), domain.CreateBookingRequest{
|
|
TenantSlug: "studio-atelier",
|
|
BookingMode: "appointment",
|
|
CustomerName: "Missing Service",
|
|
CustomerEmail: "missing@example.com",
|
|
StartsAt: time.Now().UTC().Add(24 * time.Hour).Format(time.RFC3339),
|
|
EndsAt: time.Now().UTC().Add(25 * time.Hour).Format(time.RFC3339),
|
|
})
|
|
if !errors.Is(err, ErrInvalidBooking) {
|
|
t.Fatalf("expected ErrInvalidBooking, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateClassRequiresExistingSession(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
service := NewService(repo, nil)
|
|
missingSessionID := "11111111-1111-1111-1111-111111111111"
|
|
|
|
_, err := service.Create(context.Background(), domain.CreateBookingRequest{
|
|
TenantSlug: "studio-atelier",
|
|
BookingMode: "class",
|
|
ClassSessionID: &missingSessionID,
|
|
CustomerName: "Missing Session",
|
|
CustomerEmail: "missing@example.com",
|
|
StartsAt: time.Now().UTC().Add(48 * time.Hour).Format(time.RFC3339),
|
|
EndsAt: time.Now().UTC().Add(49 * time.Hour).Format(time.RFC3339),
|
|
})
|
|
if !errors.Is(err, ErrInvalidBooking) {
|
|
t.Fatalf("expected ErrInvalidBooking, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAvailabilityGeneratesUpcomingSlots(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
service := NewService(repo, nil)
|
|
|
|
availability, err := service.Availability(context.Background(), "studio-atelier")
|
|
if err != nil {
|
|
t.Fatalf("availability: %v", err)
|
|
}
|
|
if len(availability.Slots) == 0 {
|
|
t.Fatal("expected slots")
|
|
}
|
|
|
|
for _, slot := range availability.Slots {
|
|
startsAt, err := time.Parse(time.RFC3339, slot.StartsAt)
|
|
if err != nil {
|
|
t.Fatalf("parse startsAt: %v", err)
|
|
}
|
|
if startsAt.Before(time.Now().UTC().Add(90 * time.Minute)) {
|
|
t.Fatalf("expected upcoming slot, got %s", slot.StartsAt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCreateSchedulesReminderJobForUpcomingAppointment(t *testing.T) {
|
|
repo := db.NewMemoryRepository()
|
|
service := NewService(repo, nil)
|
|
|
|
availability, err := service.Availability(context.Background(), "studio-atelier")
|
|
if err != nil {
|
|
t.Fatalf("availability: %v", err)
|
|
}
|
|
|
|
var appointment domain.TimeSlot
|
|
for _, slot := range availability.Slots {
|
|
if slot.Mode == "appointment" {
|
|
appointment = slot
|
|
break
|
|
}
|
|
}
|
|
if appointment.StartsAt == "" {
|
|
t.Fatal("expected appointment slot")
|
|
}
|
|
|
|
_, err = service.Create(context.Background(), domain.CreateBookingRequest{
|
|
TenantSlug: "studio-atelier",
|
|
BookingMode: "appointment",
|
|
ServiceID: appointment.ServiceID,
|
|
StaffID: appointment.StaffID,
|
|
LocationID: appointment.LocationID,
|
|
CustomerName: "Reminder",
|
|
CustomerEmail: "reminder@example.com",
|
|
StartsAt: time.Now().UTC().Add(30 * time.Hour).Format(time.RFC3339),
|
|
EndsAt: time.Now().UTC().Add(31 * time.Hour).Format(time.RFC3339),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
reminders, err := repo.ListDueReminderJobs(context.Background(), time.Now().UTC().Add(365*24*time.Hour), 10)
|
|
if err != nil {
|
|
t.Fatalf("list reminder jobs: %v", err)
|
|
}
|
|
if len(reminders) == 0 {
|
|
t.Fatal("expected reminder job to be scheduled")
|
|
}
|
|
}
|