//go:build test && dev package payments import ( "context" "net/http" "testing" "time" "crussell/db" "crussell/mw" "crussell/testutils" "crussell/testutils/fixtures" "crussell/testutils/jwt" "github.com/stretchr/testify/require" ) // withNonGuest wraps a payment handler in the RequireNonGuest middleware, the // same chain main.go mounts for user-facing money routes (after RequireAuth). func withNonGuest(handler http.HandlerFunc) http.HandlerFunc { return mw.RequireNonGuest(handler).ServeHTTP } // createGuestBooking sets up a guest user with an in_progress booking so a // charge would otherwise succeed if the guest guard did not block it. func createGuestBooking(t *testing.T, ctx context.Context, q db.Querier) (string, string) { guestID, err := fixtures.CreateTestGuestUser(q) require.NoError(t, err) serviceID, err := fixtures.CreateTestService(q) require.NoError(t, err) bookingID, err := fixtures.CreateTestBookingAtTime(q, guestID, serviceID, time.Date(2099, 12, 31, 10, 0, 0, 0, time.UTC)) require.NoError(t, err) _, err = q.Exec(ctx, "UPDATE bookings SET status = 'in_progress' WHERE id = $1", bookingID) require.NoError(t, err) return guestID, bookingID } func TestGuestBlocked_CreateBookingPayment(t *testing.T) { t.Parallel() ctx, tx := testutils.SetupTestTx(t) guestID, bookingID := createGuestBooking(t, ctx, tx) guestToken := jwt.GenerateTestToken(guestID, "guest") cardToken := "cnon:test-card-nonce" req := CreateBookingPaymentRequest{ Amount: 2500, PaymentType: "deposit", NewCardToken: &cardToken, IdempotencyKey: "guest-block-payment-key", } handler := withNonGuest(CreateBookingPayment) w := makePaymentRequest(handler, "POST", "/api/bookings/"+bookingID+"/payment", req, guestToken, ctx) if w.Code != http.StatusForbidden { t.Errorf("expected 403 for guest payment, got %d. body: %s", w.Code, w.Body.String()) } var count int if err := tx.QueryRow(ctx, "SELECT COUNT(*) FROM payments WHERE booking_id = $1", bookingID).Scan(&count); err != nil { t.Fatalf("failed to query payments: %v", err) } if count != 0 { t.Errorf("expected 0 payment rows for blocked guest, got %d", count) } } func TestGuestBlocked_CreateTipPayment(t *testing.T) { t.Parallel() ctx, tx := testutils.SetupTestTx(t) guestID, bookingID := createGuestBooking(t, ctx, tx) guestToken := jwt.GenerateTestToken(guestID, "guest") cardToken := "cnon:tip-card" req := CreateTipPaymentRequest{ Amount: 500, NewCardToken: &cardToken, } handler := withNonGuest(CreateTipPayment) w := makePaymentRequest(handler, "POST", "/api/bookings/"+bookingID+"/tip", req, guestToken, ctx) if w.Code != http.StatusForbidden { t.Errorf("expected 403 for guest tip, got %d. body: %s", w.Code, w.Body.String()) } } func TestGuestBlocked_BuyGiftCard(t *testing.T) { t.Parallel() ctx, tx := testutils.SetupTestTx(t) guestID, err := fixtures.CreateTestGuestUser(tx) require.NoError(t, err) guestToken := jwt.GenerateTestToken(guestID, "guest") newToken := "cnon:test-card" req := BuyGiftCardRequest{ Amount: 1000, RecipientType: "self", NewCardToken: &newToken, IdempotencyKey: "guest-block-gc-key", } handler := withNonGuest(BuyGiftCard) w := makePaymentRequest(handler, "POST", "/api/user/giftcards/buy", req, guestToken, ctx) if w.Code != http.StatusForbidden { t.Errorf("expected 403 for guest gift card purchase, got %d. body: %s", w.Code, w.Body.String()) } } func TestGuestBlocked_VerifiedUserStillPays(t *testing.T) { t.Parallel() ctx, tx := testutils.SetupTestTx(t) userID, bookingID, _ := setupTestData(t, ctx, tx) userToken := jwt.GenerateUserToken(userID) cardToken := "cnon:test-card-nonce" req := CreateBookingPaymentRequest{ Amount: 2500, PaymentType: "deposit", NewCardToken: &cardToken, SaveCard: true, IdempotencyKey: "guest-block-verified-key", } handler := withNonGuest(CreateBookingPayment) w := makePaymentRequest(handler, "POST", "/api/bookings/"+bookingID+"/payment", req, userToken, ctx) if w.Code != http.StatusOK { t.Errorf("expected 200 for verified user through RequireNonGuest, got %d. body: %s", w.Code, w.Body.String()) } var count int if err := tx.QueryRow(ctx, "SELECT COUNT(*) FROM payments WHERE booking_id = $1", bookingID).Scan(&count); err != nil { t.Fatalf("failed to query payments: %v", err) } if count != 1 { t.Errorf("expected 1 payment for verified user, got %d", count) } }