Files
Crussell/backend/handlers/payments/policy_ts_crosscheck_test.go
T
popertots 1429eddd34 fix: payments hardening — SCA wire contract (saved-card ref + tokenize-result), terminal/till token routing, tip-cap overflow carve, completion campaign atomicity, orphan B1-evidence gate, gift-card gates/locks, admin backstops
- ValidateCardInfo accepts saved-card ref + new_card_token coexistence (matches resolveChargeSource); new_card_token added to terminal/till request structs so SCA tokens are never dropped
- maxOnlineTipPence (£250) enforced on the overflow-tip carve AND buildSplitRecords (both carve paths) — closes the £10k bypass
- completion-path campaign increments made atomic reserve-first (conditional UPDATE ... RETURNING) + schema backstops (chk_times_redeemed, partial unique index on milestone redemptions)
- webhook orphan detection gated on B1 evidence (b1_attempts / sweep-duplicate refund row) so a delayed legit completion is never marked failed
- gift-card: per-user £500/day cap lock held across read-modify-write, expired-card top-up gate, NaN/Inf float bounds, refund_failed ack filter, on_the_house excluded from balance, postChargeRecheck notification
- admin apply-redemption route + admin-or-owner, in-handler isAdminRequest on 4 gift-card handlers, tip lock key aligned
- 2FA fallback machinery removed (insertTwoFAFallbackAudit/reissue/consent), dead fields stripped from charge structs
- tests: prod-tag suite, mock SCA parity, tip-cap overflow, completion races, cards pagination, ValidateCardInfo tables
2026-08-22 00:34:50 +01:00

75 lines
2.6 KiB
Go

//go:build test && dev
package payments
import (
"os"
"path/filepath"
"regexp"
"strconv"
"testing"
"github.com/stretchr/testify/require"
)
// policyTSValue extracts the numeric value of a `NAME: value,` entry from the
// frontend's POLICY object literal (policy.ts), tolerating the tab-indented
// formatting the file uses. Returns "" when the name is absent.
func policyTSValue(t *testing.T, src, name string) string {
t.Helper()
re := regexp.MustCompile(`(?m)^\s*` + regexp.QuoteMeta(name) + `:\s*(\d+(?:\.\d+)?),`)
m := re.FindStringSubmatch(src)
if m == nil {
return ""
}
return m[1]
}
// TestPolicyTS_CrossCheck pins the frontend's single-source policy constants
// (frontend/src/lib/constants/policy.ts) to THIS package's refund_policy.go
// values by REACTING to drift: the test reads the .ts file and asserts each
// entry equals the backend constant, so a one-sided change on either side fails
// CI. The frontend's own vitest suite (policy.test.ts) pins the same values in
// the other direction, closing the drift loop both ways.
func TestPolicyTS_CrossCheck(t *testing.T) {
path := filepath.Join("..", "..", "..", "frontend", "src", "lib", "constants", "policy.ts")
data, err := os.ReadFile(path)
require.NoError(t, err, "policy.ts not found at %s (tests run from the package dir; repo layout is <repo>/backend/handlers/payments + <repo>/frontend)", path)
src := string(data)
cases := []struct {
name string
want string
}{
{"REQUIRED_DEPOSIT_PCT", "0.2"},
{"PROTECTED_DEPOSIT_MAX_PCT", "0.5"},
{"FULL_REFUND_THRESHOLD_HOURS", "72"},
{"PARTIAL_REFUND_THRESHOLD_HOURS", "24"},
{"NO_SHOW_THRESHOLD_HOURS", "24"},
{"DEPOSIT_ADVANCE_HOURS", "36"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := policyTSValue(t, src, tc.name)
require.NotEmpty(t, got, "policy.ts no longer declares %q — the constant may have been renamed or removed", tc.name)
require.Equal(t, tc.want, got, "%s drifted from backend/handlers/payments/refund_policy.go", tc.name)
})
}
// The backend has no named reschedule constants; the frontend values must
// track the refund tiers they were derived from.
for _, tc := range []struct {
name string
want string
}{
{"RESCHEDULE_BLOCK_HOURS_WITH_PAYMENTS", strconv.Itoa(int(FullRefundThreshold.Hours()))},
{"RESCHEDULE_BLOCK_HOURS_NO_PAYMENTS", strconv.Itoa(int(PartialRefundThreshold.Hours()))},
} {
t.Run(tc.name, func(t *testing.T) {
got := policyTSValue(t, src, tc.name)
require.NotEmpty(t, got, "policy.ts no longer declares %q", tc.name)
require.Equal(t, tc.want, got, "%s must track the backend refund threshold", tc.name)
})
}
}