Wave 3a+4: multi-tier e2e test-user factory + Razorpay test-mode payment integration (docs/ci/01-CI-INTEGRATION-DESIGN.md)#265
Merged
mastermanas805 merged 3 commits intoJun 5, 2026
Conversation
…y test-card payment integration Wave 3a (#60) + Wave 4 (#61) api-side, per docs/ci/01-CI-INTEGRATION-DESIGN.md. Both are test-infrastructure, token/secret-gated, inert in prod. Part A — multi-tier test-user factory (extends /internal/e2e/account) - The tier param + team/growth-400 gate + paid-tier elevation already shipped in the prior factory PR; this adds the remaining brief item: with_resources. - with_resources=true pre-seeds a small set of FAST, row-only resources (webhook + cache) on the minted team — synchronous, no backend RPC, tier- snapshotted at the team's tier via the same CreateResource→MarkResourceActive two-phase lifecycle a real provision uses — so a journey can start populated. Seeded tokens surface on the response (seeded_tokens/seeded_count, always a non-null array). Rows are reaped with the team by ReapAccount. - Default tier stays "free" (NOT pro): the brief assumed current default was pro, but it has always been free — changing it would silently hand CI a paid tier and break the existing empty-body test. Kept free; documented. - Tests (registry-iterating, rule 18): every allowed tier round-trips + reflects in the minted team's plan_tier (anonymous→free team plan); every blocked tier (team/growth) → 400 tier_not_allowed; with_resources seeds exactly the handler's seed set as active/owned/tier-snapshotted rows; omitting it seeds nothing. team-tier-rejected proof retained. Part B — Razorpay test-card payment integration (Approach B, webhook-injection) - New billing_testcard_payment_test.go: real-backend, runs in the api test gate against test Postgres (NOT the live-k8s e2e suite). Razorpay TEST MODE needs no recurring approval, so this is unblocked despite the prod live-checkout operator gate. - Constructs the EXACT subscription.charged body (raw bytes, signed in place, created_at inside the ±5-min replay window), signs hex(HMAC-SHA256(rawBody, secret)) via the shared signRazorpayPayload (parity with verifyRazorpaySignature), POSTs to /razorpay/webhook with X-Razorpay-Signature + x-razorpay-event-id. - Handler maps subscription→team via resolveTeamFromNotes: notes.team_id (UUID, primary) → fallback GetTeamByRazorpaySubscriptionID(sub.id). Test stamps notes.team_id. - Asserts: tier→pro AND an active permanent resource elevated to pro (ElevateResourceTiersByTeam, inside UpgradeTeamAllTiersWithSubscription) AND the upgrade contract surfaces (plans.Registry resolves pro limits > free for the elevated resource — the /api/v1/capabilities source of truth). - Negatives: tampered body → 400 no upgrade; wrong secret → 400 no upgrade. - Idempotency: same x-razorpay-event-id twice → deduped:true + exactly one razorpay_webhook_events row + tier upgraded once. - Failure path: payment.failed → 200, NO upgrade (state asserted, not delivery; failure email is webhook-gated per project_payment_failure_email_coverage). Verify: go build/vet ./... green; new factory + payment tests pass on a fresh test DB. (Local full -p 1 surfaces pre-existing pollution flakes in untouched packages — models TestLinkGitHubID, storage anon-quota cap — that pass on a clean DB; CI's ephemeral DB is authoritative.) Awaiting web wave to drive UI journeys against the multi-tier factory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI diff-cover flagged 80% patch coverage: the seed-failure branches were uncovered (internal_e2e_account.go:297-300 seed_failed 503 arm; 374-379 CreateResource/MarkResourceActive error returns). - Add e2eSeedFastResources package-var seam (mirrors the e2eSignSessionJWT seam) so a test can force CreateAccount's seed_failed 503 arm without making the real resources table reject an insert mid-request. - TestE2EAccount_Create_WithResources_SeedFailure_Returns503: seam → 503 seed_failed. - internal_e2e_account_seed_whitebox_test.go (sqlmock): the two seedFastResources error arms — CreateResource error + MarkResourceActive error after a successful insert. seedFastResources + CreateAccount now report 100.0% func coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s for the seed path build-and-test surfaced two registry-iterating guard failures from the new with_resources seed path: - TestEveryCreateResourceCallSiteIsFollowedByFinalizeProvision: seedFastResources calls models.CreateResource without finalizeProvision. That guard targets the orphan-generator shape (insert row → backend provision → 201 without atomic credential persistence). The seed is CI-only, row-only (webhook/cache), has NO backend RPC and NO credential to persist, so the shape can't occur. Added internal_e2e_account.go to the allowList with a justifying comment naming the alternate path (CreateResource→MarkResourceActive two-phase lifecycle). - TestErrorCode_HasAgentAction: the new seed_failed code had no codeToAgentAction entry. Added it to coverageAllowlist alongside the other CI-only /internal/e2e/account codes (machine-to-machine, not customer-facing). Both guards green locally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
api-side of two CI waves from
docs/ci/01-CI-INTEGRATION-DESIGN.md. Both are test-infrastructure, token/secret-gated, inert in prod.Part A — multi-tier test-user factory (Wave 3a, #60)
Extends the guarded
POST /internal/e2e/account. Thetierparam, theteam/growth→ 400 gate, and paid-tier elevation already shipped in the prior factory PR. This PR adds the remaining brief item,with_resources:with_resources:truepre-seeds a small set of FAST, row-only resources (webhook+cache) on the minted team — synchronous, no backend RPC, tier-snapshotted via the sameCreateResource → MarkResourceActivetwo-phase lifecycle a real provision uses — so a CI journey can start from a populated account.seeded_tokens/seeded_count, always a non-null array). Rows are reaped with the team byReapAccount.free(the brief assumedprowas the current default; it has always beenfree— changing it would silently hand CI a paid tier and break the existing empty-body test). Documented in the commit.plan_tier; every blocked tier →400 tier_not_allowed;with_resourcesseeds exactly the handler's seed set; omitting it seeds nothing.Part B — Razorpay test-card payment integration (Wave 4, #61)
New
internal/handlers/billing_testcard_payment_test.go— Approach B (deterministic webhook-injection). Real-backend, runs in the api test gate against test Postgres. Razorpay TEST MODE needs no recurring approval, so this is unblocked despite the prod live-checkout operator gate.subscription.chargedbody (raw bytes, signed in place,created_atinside the ±5-min replay window), signshex(HMAC-SHA256(rawBody, secret))via the sharedsignRazorpayPayload(parity withverifyRazorpaySignature), POSTs withX-Razorpay-Signature+x-razorpay-event-id.resolveTeamFromNotes):notes.team_id(UUID, primary) → fallbackGetTeamByRazorpaySubscriptionID(sub.id). Test stampsnotes.team_id.ElevateResourceTiersByTeam) AND the upgrade contract surfaces (plans.Registryresolves pro limits > free — the/api/v1/capabilitiessource of truth).x-razorpay-event-idtwice →deduped:true+ exactly onerazorpay_webhook_eventsrow + upgraded once.payment.failed→ 200, NO upgrade (state asserted, not delivery — failure email is webhook-gated).Verify
go build ./...+go vet ./...green module-wide.X-E2E-Token; the payment test uses the test webhook secret, never prod.Design ref:
docs/ci/01-CI-INTEGRATION-DESIGN.md(§The test-user FACTORY, §Razorpay test-card payment E2E). Follow-up: web wave drives UI journeys against the multi-tier factory.🤖 Generated with Claude Code