rentease/internal/service/payment/stripe_sync_test.go
Ruidy 146787033a
refactor(payment): extract payment logic to new service
Moves all payment-related logic (manual payments, Stripe sync, webhook
handling) from the booking service into a dedicated payment service
(`internal/service/payment`). Updates server, cron, and handler wiring
to
inject and use the new payment service. Adjusts tests, routes, and
documentation to reflect the new separation of concerns.

This improves cohesion, clarifies responsibilities, and prepares for
future payment features. No database schema changes are introduced.
2025-11-21 10:09:30 +01:00

157 lines
4.2 KiB
Go

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")
}
}