Skip to content

fix(api): detect live 'healthy' stacks + reject 200 ok:false promo codes#197

Merged
mastermanas805 merged 1 commit into
mainfrom
fix/stack-healthy-status-and-promo-validation
Jun 6, 2026
Merged

fix(api): detect live 'healthy' stacks + reject 200 ok:false promo codes#197
mastermanas805 merged 1 commit into
mainfrom
fix/stack-healthy-status-and-promo-validation

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

Fixes two UI↔API contract-mismatch bugs in src/api/index.ts found by the 2026-06-06 bug-hunt pass (docs/ci/BUGHUNT-BACKLOG-2026-06-06.md P1-2, P1-3). Both touch the same file, so they share one PR.

Bug 1 (P1-2) — StackCreatePage never detected a live stack

The api sets a successfully-deployed stack's status to "healthy" (api/internal/handlers/stack.go:382), but the dashboard's StackStatus union omits 'healthy' and the create-page poll (StackCreatePage.tsx:210/:169) + fetchStackStatus/listStacks only treat === 'running' as live. A successful dashboard stack deploy therefore spun in the building stage until the ~5-min poll timeout instead of flipping to live.

Fix: add normaliseStackStatus() mirroring the existing normaliseDeploymentStatus ('healthy'/'deploying''running', 'deleting''stopped', unknown → 'building'), applied in fetchStackStatus(), listStacks(), and createStack(). The type and the runtime value now agree in one place; the page's existing === 'running' live-checks trip for a live stack with no page change.

Bug 2 (P1-3) — invalid promo codes validated as "ok"

POST /api/v1/billing/promotion/validate is LIVE and reports validation FAILURES as 200 ok:false (not a 4xx — confirmed in billing_promotion.go:216 and the OpenAPI snapshot). call() only throws on non-2xx, so validatePromotion built a phantom "valid" promo (empty/undefined discount) from an ok:false body; the 404→seeds fallback was dead code (the handler never 404s).

Fix: inspect ok — a 200 ok:false (or ok:true without a discount) throws an APIError carrying the api's error/message so the UI renders the real rejection. Removed the dead seed table; corrected the stale // mocked until api ships header comment (P3-2) and the misleading // LIVE comment on cancelSubscription (P3-1/billing/cancel was removed by policy). Money bug once Razorpay offers wire up.

Tests — fail-before / pass-after (proven)

src/api/index.test.ts. Stashing the src fix and re-running confirmed:

  • stack: healthyrunning, deployingrunning, deletingstopped, unknown→building cases fail against old code (in fetchStackStatus, listStacks, createStack).
  • promo: the 200 ok:false rejection cases + the malformed ok:true-no-discount case fail against old code.

100% patch coverage on both changed regions (verified via v8 coverage JSON — zero uncovered statements/branches in normaliseStackStatus and validatePromotion). Full npm run gate GREEN (tsc + build + prerender + 1141 vitest).

🤖 Generated with Claude Code

Two UI↔API contract-mismatch bugs in src/api/index.ts, both surfaced by the
2026-06-06 bug-hunt pass (docs/ci/BUGHUNT-BACKLOG-2026-06-06.md P1-2, P1-3).

Bug 1 (P1-2) — StackCreatePage never detected a live stack.
The api emits status="healthy" for a successfully-deployed stack
(stack.go:382), but the dashboard's StackStatus union omits 'healthy' and the
create-page poll only treats === 'running' as live. A successful dashboard
stack deploy therefore spun in the 'building' stage until the ~5-min poll
timeout instead of flipping to 'live'. Add normaliseStackStatus() mirroring
normaliseDeploymentStatus ('healthy'/'deploying' → 'running', 'deleting' →
'stopped') and apply it in fetchStackStatus(), listStacks(), and createStack()
so the type and runtime value agree in one place; the page's existing
=== 'running' checks now trip for a live stack.

Bug 2 (P1-3) — invalid promo codes validated as "ok".
POST /api/v1/billing/promotion/validate is LIVE and reports validation
FAILURES as 200 ok:false (not 4xx). call() only throws on non-2xx, so
validatePromotion built a phantom "valid" promo (empty discount) from an
ok:false body; the 404→seeds fallback was dead code (handler never 404s).
Now inspect `ok`: a 200 ok:false (or ok:true without a discount) throws an
APIError carrying the api's error/message so the UI renders the real
rejection. Drop the dead seed table + fix the stale "mocked until api ships"
header comment (P3-2). Also correct the misleading "LIVE" comment on
cancelSubscription (P3-1 — the /billing/cancel route was removed by policy).

Tests (src/api/index.test.ts): proven fail-before/pass-after by stashing the
src fix — the 'healthy'/'deploying' stack cases and the 200 ok:false promo
cases fail against old code. 100% patch coverage on both changed regions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mastermanas805
mastermanas805 enabled auto-merge (squash) June 6, 2026 06:00
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
dist/assets/index-DV4gJTWd.js 0 B (-100% 🔽)
dist/assets/index-BsJUZYRr.css 6.13 KB (0%)
dist/assets/index-BZLn7_NF.js 162.22 KB (+100% 🔺)

@mastermanas805
mastermanas805 merged commit 8bfa90b into main Jun 6, 2026
19 checks passed
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