package booking import ( "errors" "log/slog" "testing" "time" "github.com/rjNemo/rentease/internal/config" ) func TestParseFromAPI_CreatesItemsForBookingSync(t *testing.T) { store := newStubStore() parser := stubParser{ booking: Booking{ From: time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC), To: time.Date(2025, time.January, 4, 0, 0, 0, 0, time.UTC), Name: "Jane Doe", PhoneNumber: "123456", Email: "jane@example.com", Platform: config.Platform("Booking"), CustomerNumber: 2, PlatformFees: 15.0, Items: []Item{{ Item: " T3 ", Quantity: 3, Price: 80.0, PaymentMethod: "Card", }}, }, } svc, err := NewService(slog.Default(), store, parser, noopPDF{}) if err != nil { t.Fatalf("unexpected error creating service: %v", err) } booking, err := svc.ParseFromAPI("raw booking content") if err != nil { t.Fatalf("unexpected error parsing booking: %v", err) } if len(store.items) != 2 { t.Fatalf("expected 2 items to be created (booking item + taxes), got %d", len(store.items)) } if store.items[0].Item != "T3" { t.Fatalf("expected base item to be trimmed to host item name, got %q", store.items[0].Item) } if store.items[1].Item != "Taxes" { t.Fatalf("expected taxes item to be created, got %q", store.items[1].Item) } if len(booking.Items) != 2 { t.Fatalf("expected booking to include created items, got %d", len(booking.Items)) } } func TestParseFromAPI_DoesNotCreateTaxesForNonBookingPlatform(t *testing.T) { store := newStubStore() parser := stubParser{ booking: Booking{ From: time.Date(2025, time.March, 1, 0, 0, 0, 0, time.UTC), To: time.Date(2025, time.March, 3, 0, 0, 0, 0, time.UTC), Name: "Alex Smith", PhoneNumber: "987654", Email: "alex@example.com", Platform: config.Platform("AirBnb"), CustomerNumber: 2, Items: []Item{{ Item: "t2", Quantity: 2, Price: 59.0, PaymentMethod: "Card", }}, }, } svc, err := NewService(slog.Default(), store, parser, noopPDF{}) if err != nil { t.Fatalf("unexpected error creating service: %v", err) } if _, err := svc.ParseFromAPI("raw booking content"); err != nil { t.Fatalf("unexpected error parsing booking: %v", err) } if len(store.items) != 1 { t.Fatalf("expected only base item without taxes for non-Booking platform, got %d items", len(store.items)) } if store.items[0].Item != "T2" { t.Fatalf("expected base item to match host item name, got %q", store.items[0].Item) } } func TestParseFromAPI_CreatesFallbackItemWhenUnknown(t *testing.T) { store := newStubStore() parser := stubParser{ booking: Booking{ From: time.Date(2025, time.February, 1, 0, 0, 0, 0, time.UTC), To: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC), Name: "Jane Doe", PhoneNumber: "123456", Email: "jane@example.com", Platform: config.Platform("Booking"), CustomerNumber: 1, Items: []Item{{ Item: "New Suite", Quantity: 1, Price: 120.0, PaymentMethod: "Card", }}, }, } svc, err := NewService(slog.Default(), store, parser, noopPDF{}) if err != nil { t.Fatalf("unexpected error creating service: %v", err) } if _, err := svc.ParseFromAPI("raw booking content"); err != nil { t.Fatalf("unexpected error parsing booking: %v", err) } if len(store.items) != 1 { t.Fatalf("expected fallback item to be created, got %d items", len(store.items)) } if store.items[0].Item != "New Suite" { t.Fatalf("expected fallback item name to match parsed value, got %q", store.items[0].Item) } } func TestParseFromAPI_NormalizesVerboseItemNameToHostItem(t *testing.T) { store := newStubStore() parser := stubParser{ booking: Booking{ From: time.Date(2025, time.April, 1, 0, 0, 0, 0, time.UTC), To: time.Date(2025, time.April, 3, 0, 0, 0, 0, time.UTC), Name: "Chris P Bacon", PhoneNumber: "999999", Email: "chris@example.com", Platform: config.Platform("Booking"), CustomerNumber: 2, Items: []Item{{ Item: "One-Bedroom House (T2 - VillaFleurie au bourg du Gosier)", Quantity: 2, Price: 59.0, PaymentMethod: "Card", }}, }, } svc, err := NewService(slog.Default(), store, parser, noopPDF{}) if err != nil { t.Fatalf("unexpected error creating service: %v", err) } if _, err := svc.ParseFromAPI("raw booking content"); err != nil { t.Fatalf("unexpected error parsing booking: %v", err) } if len(store.items) != 2 { t.Fatalf("expected base item plus taxes, got %d items", len(store.items)) } if store.items[0].Item != "T2" { t.Fatalf("expected verbose item name to normalize to host item 'T2', got %q", store.items[0].Item) } } type stubParser struct { booking Booking err error } func (p stubParser) Parse(rawContent string) (*Booking, error) { if p.err != nil { return nil, p.err } cp := p.booking return &cp, nil } type stubStore struct { bookings []*Booking items []*Item } func newStubStore() *stubStore { return &stubStore{ bookings: make([]*Booking, 0), items: make([]*Item, 0), } } func (s *stubStore) All() []*Line { return nil } func (s *stubStore) Search(value string) []*Line { return nil } func (s *stubStore) List(from, to time.Time) ([]*Line, error) { return nil, nil } func (s *stubStore) CardTotal(from, to time.Time) (float64, error) { return 0, nil } func (s *stubStore) Get(id int) (*Booking, error) { return nil, errors.New("not implemented") } func (s *stubStore) Create(b *Booking) error { b.ID = len(s.bookings) + 1 s.bookings = append(s.bookings, b) return nil } func (s *stubStore) Update(b *Booking) error { return nil } func (s *stubStore) Cancel(id int) error { return nil } func (s *stubStore) CreateItem(i *Item) error { i.ID = len(s.items) + 1 s.items = append(s.items, i) return nil } func (s *stubStore) PayItem(id int) (*Item, error) { return nil, nil } func (s *stubStore) GetItem(id int) (*Item, error) { return nil, nil } func (s *stubStore) UpdateItem(id int, item string, paymentMethod string, paymentStatus string, qty int, price float64) (*Item, error) { return nil, nil } type noopPDF struct{} func (noopPDF) BuildInvoice(invoice Invoice) (string, error) { return "", nil } func (noopPDF) BuildReport(report ReportData, period string, month, year int) (string, error) { return "", nil }