Skip to content

test(e2e): codify end-to-end customer flow into a regression test - #20

Closed
mastermanas805 wants to merge 1 commit into
masterfrom
test/customer-flow-e2e
Closed

mastermanas805 wants to merge 1 commit into
masterfrom
test/customer-flow-e2e

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

Summary

Codifies the manual end-to-end customer flow that was hand-driven to verify the instanode.dev funnel works. Three new tests in e2e/customer_flow_e2e_test.go (gated by //go:build e2e):

  • TestE2E_FullCustomerFlow_AnonymousToProToCancelled — 11-step happy path:

    1. anonymous POST /db/new → captures upgrade_jwt + token
    2. POST /claim with randomized email → asserts ok:true, session_token, team_id
    3. GET /api/v1/whoami with session token → asserts tier=hobby, plan_tier=hobby, email, team_id
    4. GET /api/v1/billing → asserts tier=hobby, subscription_status=trial
    5. GET /api/v1/resources → asserts claimed postgres present at tier=hobby
    6. signed subscription.charged to /razorpay/webhook → asserts 200 ok:true
    7. /api/v1/billing → asserts tier=pro, subscription_status=active
    8. /api/v1/resources → asserts all active items elevated to tier=pro (proves ElevateResourceTiersByTeam)
    9. signed subscription.cancelled → asserts 200
    10. /api/v1/billing → asserts tier=hobby (downgrade)
    11. /api/v1/resources → asserts existing resources KEEP tier=pro (documented snapshot behaviour per CLAUDE.md)
  • TestE2E_FullCustomerFlow_WhoamiBeforeClaim — regression guard: anonymous upgrade_jwt against /whoami must 401 (proves anonymous tokens can't auth against the dashboard surface).

  • TestE2E_FullCustomerFlow_StoragePathReturnsSpacesCreds — /storage/new returns S3-compatible endpoint + access_key_id + secret_access_key + prefix, and the public endpoint does NOT contain "minio" (post-Spaces switch sanity check).

All three t.Skip() cleanly when required env is absent. Uses existing helpers (uniqueIP, post, get, decodeJSON, subscriptionChargedPayload, subscriptionCancelledPayload, signRazorpayPayload).

Required env to actually run

Var Purpose
E2E_BASE_URL live server (defaults to http://localhost:32108)
E2E_JWT_SECRET required for /whoami and management API
E2E_RAZORPAY_WEBHOOK_SECRET HMAC key for the webhook payloads
E2E_RAZORPAY_PLAN_ID_PRO optional — plan_id placed in notes; handler defaults to "pro" when empty

Test plan

  • go test -tags e2e -c ./e2e -o /dev/null — compiles
  • All 3 tests run against the live k8s cluster: 3/3 PASS in 26s wall-clock with the secrets pulled from instant-secrets
  • Tests skip cleanly when secrets absent (verified by razorpayWebhookSecret(t) + explicit E2E_JWT_SECRET check)
  • CI runs the full suite once secrets are wired in the workflow

🤖 Generated with Claude Code

Captures the full anonymous → claim → upgrade → cancel funnel in a single
runnable test. Codifies the contract for /claim's session_token, /whoami's
tier+email enrichment, /api/v1/billing's trial/active/cancelled status,
and ElevateResourceTiersByTeam's promote-on-upgrade behaviour.

Adds three tests:

  - TestE2E_FullCustomerFlow_AnonymousToProToCancelled — 11-step happy
    path. Provisions /db/new anonymously, claims with email, uses the
    returned session_token to hit /whoami, /billing, /resources, fires
    a subscription.charged webhook, asserts tier flips to pro on all
    surfaces and existing resources are elevated, fires
    subscription.cancelled, asserts downgrade to hobby and that
    existing resources keep tier=pro (documented snapshot behaviour).

  - TestE2E_FullCustomerFlow_WhoamiBeforeClaim — regression guard that
    the anonymous upgrade_jwt cannot auth against /api/v1/whoami; must
    return 401.

  - TestE2E_FullCustomerFlow_StoragePathReturnsSpacesCreds — post-Spaces
    switch sanity check that /storage/new returns S3-compatible creds
    and the public endpoint does not leak the in-cluster MinIO hostname.

All three skip cleanly when the required secrets are absent
(E2E_JWT_SECRET, E2E_RAZORPAY_WEBHOOK_SECRET) and pass against the live
local k8s cluster (verified: 26s wall-clock, 3/3 PASS).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mastermanas805

Copy link
Copy Markdown
Member Author

Rebase attempt 2026-05-20 hit conflicts vs current master (6b9e817). Today's session shipped 5 tracks of integration tests in api/e2e/ (backup_restore, brevo_webhook, readyz, propagation, reliability_contract) per INTEGRATION-TESTS-2026-05-20.md. The customer-flow scenarios in this PR may overlap with Track 5 ("no orphan kinds" contract test). Worth a manual review to see if the unique value here is still uncovered — if not, close.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

@mastermanas805

Copy link
Copy Markdown
Member Author

Closing as superseded — PR #20's content was forward-merged via #21 (477e3c2, 2026-05-12) and then further refined by W10 trial-removal (bf60e17, #72).

Scenario coverage citations

All three tests are present on master at api/e2e/customer_flow_e2e_test.go with the same function names and a strict improvement (post-trial-removal policy):

PR #20 test Master location Status
TestE2E_FullCustomerFlow_AnonymousToProToCancelled e2e/customer_flow_e2e_test.go:81 Present + improved
TestE2E_FullCustomerFlow_WhoamiBeforeClaim e2e/customer_flow_e2e_test.go:339 Present, identical
TestE2E_FullCustomerFlow_StoragePathReturnsSpacesCreds e2e/customer_flow_e2e_test.go:380 Present, identical

Forward-port detail (the +8-line drift)

The branch's step 4 asserts subscription_status="trial". After W10 deep-removed the trial concept (per project_no_trial_pay_day_one.md — "hobby/pro/team are paid from signup, no trial period"), master's step 4 now asserts subscription_status="none" and actively guards against "trial" ever reappearing:

status, _ := billing["subscription_status"].(string)
if status == "trial" {
    t.Errorf("step 4: /billing subscription_status must never be 'trial' — no trial period exists on the platform")
}
if status != "none" {
    t.Errorf("step 4: /billing subscription_status: want none, got %q", status)
}

Re-opening this branch would regress the trial-policy guard. Closing.

Adjacent integration tests (Track 5 from INTEGRATION-TESTS-2026-05-20.md)

PR #20's scope (customer flow happy-path + 2 regression guards) is orthogonal to Track 5's reliability integration suite — backup/restore, brevo webhook, readyz, reliability contract, propagation chaos all cover different surfaces. No cross-coverage gaps exist that PR #20 would fill.

Coverage block:

Symptom:        end-to-end customer funnel regression (anon→claim→pro→cancel)
Enumeration:    rg -n '^func Test' api/e2e/customer_flow_e2e_test.go
Sites found:    3 test funcs (all 3 from PR #20)
Sites touched:  0 needed — already on master
Coverage test:  TestE2E_FullCustomerFlow_* (3 funcs, runs under -tags e2e)
Live verified:  N/A — no port required; master commit 477e3c2 has been live since 2026-05-12

mastermanas805 added a commit that referenced this pull request Jun 2, 2026
… 404 leak, Team dedicated (#218)

* fix(api): bug-bash batch — free TTL, brevo non-clobber, dedup expiry, 404 leak, Team dedicated

Five confirmed bugs from the 2026-06-02 platform bug bash:

- #4 (P1) free-tier resources never expired: authenticated provisions
  hardcoded ExpiresAt=nil even for free/anonymous tiers. Add
  resourceExpiryForTier (24h for ephemeral tiers, nil for paid) and apply it
  at all 10 authenticated CreateResource sites. Per product decision: enforce
  plans.yaml's documented 24h TTL for claimed-unpaid resources.
- #6 (P1) Brevo 'delivered' webhook clobbered a terminal bounce/complaint on
  out-of-order delivery, corrupting the email truth surface (rule 12). Guard
  the UPDATE against terminal classes; distinguish terminal-kept from unknown.
- #17/#20 (P2) fingerprint dedup-return handed back credentials for
  active-but-expired anonymous resources: add the expires_at filter to both
  GetActiveResourceByFingerprint[Type], matching GetAllActiveResourcesByFingerprint.
- #22 (P3) deploy CancelDelete returned 403 cross-tenant (leaking existence);
  now 404 like the other deploy endpoints.
- #12 (P2) Team tier gets dedicated infra: add dedicated:true to team +
  team_yearly in plans.yaml (pairs with common defaultYAML). Per product
  decision 2026-06-02.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(brevo): cover #6 terminal-kept non-clobber path + fix delivered mocks

The delivered UPDATE now carries the terminal-class guard (8 args) and a 0-row
result triggers an existence probe. Update expectDeliveredUpdate to the new arg
list, add the SELECT mock to the unknown-message test, and add a terminal-kept
regression (delivered-after-bounce → matched:true, class preserved). Closes the
batch-1 patch-coverage gap on brevo_webhook.go.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(api): update existing tests for batch-1 behavior changes

CI (full real-DB suite) caught three existing tests that encoded the
pre-fix behavior batch-1 deliberately changed:
- TestPlansRegistry_IsDedicatedTier: team is now dedicated (#12).
- TestDeployCancelDelete_CrossTeam: cross-tenant now 404 not 403 (#22).
- TestBrevo_Receive_UnknownMessageID (billing_coverage): delivered UPDATE now
  carries the terminal-class guard (8 args) + an existence-probe SELECT (#6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(brevo): cover delivered-handler SELECT-probe error branch (#218 coverage)

diff-cover flagged brevo_webhook.go:530-531 — the `if qErr != nil` arm of the
delivered handler's existence probe (bug bash #6). When the terminal-class-
guarded UPDATE affects 0 rows, a follow-up SELECT distinguishes terminal-kept
from genuinely-unknown; a non-ErrNoRows fault on that probe must surface as an
error (→ 500) so Brevo retries rather than the message being mislabeled.

Adds TestBrevo_Receive_Delivered_ProbeError (sqlmock, hermetic): UPDATE → 0
rows, SELECT probe → generic error, asserts 500.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant