package booking import ( "context" "errors" "log/slog" "testing" "time" "gorm.io/gorm" "github.com/rjNemo/rentease/internal/config" stripeclient "github.com/rjNemo/rentease/internal/driver/stripe" ) type fakeStripeClient struct { payments []stripeclient.Payment err error } func (f *fakeStripeClient) ListPayments(ctx context.Context, params stripeclient.ListPaymentsParams) ([]stripeclient.Payment, error) { return f.payments, f.err } type mockStore struct { upserts []*Payment err error byStripeID map[string]*Payment } func (m *mockStore) record(p *Payment) (*Payment, error) { cp := *p m.upserts = append(m.upserts, &cp) if cp.StripePaymentID != nil { if m.byStripeID == nil { m.byStripeID = make(map[string]*Payment) } clone := cp m.byStripeID[*cp.StripePaymentID] = &clone } if m.err != nil { return nil, m.err } return &cp, nil } func (m *mockStore) All() []*Line { return nil } func (m *mockStore) Search(string) []*Line { return nil } func (m *mockStore) List(time.Time, time.Time) ([]*Line, error) { return nil, nil } func (m *mockStore) CardTotal(time.Time, time.Time) (float64, error) { return 0, nil } func (m *mockStore) Get(int) *Booking { return nil } func (m *mockStore) Create(*Booking) error { return nil } func (m *mockStore) Update(*Booking) error { return nil } func (m *mockStore) Cancel(int) error { return nil } func (m *mockStore) CreateItem(*Item) error { return nil } func (m *mockStore) PayItem(int) (*Item, error) { return nil, nil } func (m *mockStore) GetItem(int) (*Item, error) { return nil, nil } func (m *mockStore) UpdateItem(int, string, string, string, int, float64) (*Item, error) { return nil, nil } func (m *mockStore) CreatePayment(*Payment) (*Payment, error) { return nil, nil } func (m *mockStore) GetPayment(int) (*Payment, error) { return nil, nil } func (m *mockStore) UpdatePayment(int, float64, string) (*Payment, error) { return nil, nil } func (m *mockStore) UpsertStripePayment(p *Payment) (*Payment, error) { return m.record(p) } func (m *mockStore) FindStripePayment(id string) (*Payment, error) { if m.byStripeID == nil { return nil, gorm.ErrRecordNotFound } if p, ok := m.byStripeID[id]; ok { clone := *p return &clone, nil } return nil, gorm.ErrRecordNotFound } func TestSyncStripePayments(t *testing.T) { bookingID := uint(42) stripePayments := []stripeclient.Payment{ { ID: "pi_123", Amount: 120.50, PaymentMethod: "card", Status: "succeeded", BookingID: &bookingID, }, } store := &mockStore{} stripe := &fakeStripeClient{payments: stripePayments} logger := slog.New(slog.DiscardHandler) svc, err := NewService(logger, store, nil, nil, stripe) if err != nil { t.Fatalf("NewService returned error: %v", err) } if err := svc.SyncStripePayments(context.Background(), time.Now().Add(-time.Hour), time.Now()); err != nil { t.Fatalf("SyncStripePayments returned error: %v", err) } if len(store.upserts) != 1 { t.Fatalf("expected 1 upsert, got %d", len(store.upserts)) } upsert := store.upserts[0] if upsert.Amount != 120.50 { t.Errorf("unexpected amount: %v", upsert.Amount) } if upsert.PaymentMethod != config.PaymentMethod("Card") { t.Errorf("unexpected payment method: %v", upsert.PaymentMethod) } if upsert.StripePaymentID == nil || *upsert.StripePaymentID != "pi_123" { t.Errorf("stripe payment id not set correctly: %v", upsert.StripePaymentID) } } func TestSyncStripePaymentsSkipsMissingBooking(t *testing.T) { stripePayments := []stripeclient.Payment{ {ID: "pi_123", Amount: 10}, } store := &mockStore{} stripe := &fakeStripeClient{payments: stripePayments} logger := slog.New(slog.DiscardHandler) svc, err := NewService(logger, store, nil, nil, stripe) if err != nil { t.Fatalf("NewService returned error: %v", err) } if err := svc.SyncStripePayments(context.Background(), time.Now().Add(-time.Hour), time.Now()); err != nil { t.Fatalf("SyncStripePayments returned error: %v", err) } if len(store.upserts) != 0 { t.Fatalf("expected 0 upserts, got %d", len(store.upserts)) } } func TestSyncStripePaymentsReturnsAggregatedError(t *testing.T) { bookingID := uint(7) stripePayments := []stripeclient.Payment{ { ID: "pi_err", Amount: 50, PaymentMethod: "card", Status: "succeeded", BookingID: &bookingID, }, } store := &mockStore{err: errors.New("db failure")} stripe := &fakeStripeClient{payments: stripePayments} logger := slog.New(slog.DiscardHandler) svc, err := NewService(logger, store, nil, nil, stripe) if err != nil { t.Fatalf("NewService returned error: %v", err) } err = svc.SyncStripePayments(context.Background(), time.Now().Add(-time.Hour), time.Now()) if err == nil { t.Fatalf("expected error, got nil") } }