package payment import ( "context" "errors" "log/slog" "testing" "time" "gorm.io/gorm" "github.com/rjNemo/rentease/internal/config" "github.com/rjNemo/rentease/internal/driver/stripe" "github.com/rjNemo/rentease/internal/service/booking" ) type fakeStripeClient struct { payments []stripe.Payment err error } func (f *fakeStripeClient) ListPayments(ctx context.Context, params stripe.ListPaymentsParams) ([]stripe.Payment, error) { return f.payments, f.err } func (f *fakeStripeClient) CreatePaymentLink(ctx context.Context, params stripe.CreatePaymentLinkParams) (string, error) { return "", nil } 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) Get(int) (*booking.Booking, 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 := []stripe.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, 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 := []stripe.Payment{ {ID: "pi_123", Amount: 10}, } store := &mockStore{} stripe := &fakeStripeClient{payments: stripePayments} logger := slog.New(slog.DiscardHandler) svc, err := NewService(logger, store, 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 := []stripe.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, 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") } }