Expand payment test coverage: lock contention, nonce-direct, provisional rows, GDPR scrub, validators
Close the coverage-gap round: terminal CreateCheckout-failure marks the provisional row failed, GetCheckoutStatus reference_id mismatch 400, deadline wire shape, concurrent loyalty redemption 409, delete_guest_user + stale-guest saved-card scrubbing, ValidateAmount and isTokenLike direct units, bounded try-lock timeout, buildSplitRecords tip-overflow, and concurrent same-key dedup for gift card / booking / tip / checkout.
This commit is contained in:
@@ -809,3 +809,62 @@ func TestGetCheckoutStatus_TwoEqualAmountCharges_NoCollision(t *testing.T) {
|
||||
t.Errorf("expected exactly 2 payment rows for two equal-amount charges, got %d", rowCount)
|
||||
}
|
||||
}
|
||||
|
||||
// mismatchedRefCheckoutClient forces GetCheckout to return a fixed COMPLETED
|
||||
// payment whose reference_id points at a DIFFERENT booking, deterministically
|
||||
// exercising GetCheckoutStatus's ownership check.
|
||||
type mismatchedRefCheckoutClient struct {
|
||||
square.SquareClient
|
||||
result *square.PaymentResult
|
||||
}
|
||||
|
||||
func (c *mismatchedRefCheckoutClient) GetCheckout(ctx context.Context, checkoutID string) (*square.PaymentResult, error) {
|
||||
return c.result, nil
|
||||
}
|
||||
|
||||
func TestGetCheckoutStatus_ReferenceIDMismatch_Returns400(t *testing.T) {
|
||||
// The terminal checkout's reference_id must match the booking being
|
||||
// polled; a checkout that references a different booking is refused with
|
||||
// 400 so its payment can never be recorded against the wrong booking.
|
||||
ctx, tx := testutils.SetupTestTx(t)
|
||||
_, bookingID, _ := setupTestData(t, ctx, tx)
|
||||
|
||||
origClient := SquareClient
|
||||
SquareClient = &mismatchedRefCheckoutClient{
|
||||
SquareClient: square.NewDevClient(),
|
||||
result: &square.PaymentResult{
|
||||
ID: "pay_mismatch",
|
||||
Status: "COMPLETED",
|
||||
Amount: 5000,
|
||||
SquarePayID: "pay_mismatch",
|
||||
ReferenceID: "00000000dead", // a DIFFERENT booking
|
||||
CreatedAt: "2026-07-31T00:00:00Z",
|
||||
UpdatedAt: "2026-07-31T00:00:00Z",
|
||||
},
|
||||
}
|
||||
defer func() { SquareClient = origClient }()
|
||||
|
||||
checkoutID := "abcd1234ef56" // 12 hex chars, passes the checkout-id validation
|
||||
req := httptest.NewRequest("GET", "/api/admin/payments/"+checkoutID+"/status?booking_id="+bookingID, nil)
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("checkout_id", checkoutID)
|
||||
reqCtx := context.WithValue(req.Context(), chi.RouteCtxKey, rctx)
|
||||
reqCtx = context.WithValue(reqCtx, mw.UserIDKey, "000000000001")
|
||||
reqCtx = context.WithValue(reqCtx, mw.UserRoleKey, "admin")
|
||||
req = req.WithContext(reqCtx)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
GetCheckoutStatus(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for reference_id mismatch, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// No payment may be recorded for the mismatched checkout.
|
||||
var rowCount int
|
||||
if err := tx.QueryRow(ctx, "SELECT COUNT(*) FROM payments WHERE booking_id = $1", bookingID).Scan(&rowCount); err != nil {
|
||||
t.Fatalf("failed to count payment rows: %v", err)
|
||||
}
|
||||
if rowCount != 0 {
|
||||
t.Errorf("expected no payment rows after reference mismatch, got %d", rowCount)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user