Skip to main content

test-convt-web

Run and verify the convt.app website (apps/web, TanStack Start on Cloudflare Workers) and its billing Worker (apps/billing) locally, with Postgres, Mailpit, the OAuth mock and the Polar and Resend mock, signed in as a fixture account, in a browser at desktop and mobile widths. Use when changing anything under apps/web, apps/billing, packages/db, packages/billing, packages/mail, packages/license, tools/oauth-mock or tools/billing-mock, or when asked to preview, screenshot or check the site.

معلومات المصدر

المستودع
opencoredev/convt
آخر نشاط في المصدر
٦ أكتوبر ٢٠٢٦ في ٠١:١٧
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢٢٩
التفرعات
١١

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
2 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
test-convt-web
description
Run and verify the convt.app website (apps/web, TanStack Start on Cloudflare Workers) and its billing Worker (apps/billing) locally, with Postgres, Mailpit, the OAuth mock and the Polar and Resend mock, signed in as a fixture account, in a browser at desktop and mobile widths. Use when changing anything under apps/web, apps/billing, packages/db, packages/billing, packages/mail, packages/license, tools/oauth-mock or tools/billing-mock, or when asked to preview, screenshot or check the site.
# Test the convt website `apps/web` is a TanStack Start app built with Vite and the Cloudflare plugin, deployed with Wrangler as the `convt-web` Worker. `apps/billing` is the `convt-billing` Worker: the Polar webhook route, the billing crons, and the `BillingRpc` entrypoint the site calls through its `BILLING` service binding. It holds every billing secret and the license signing key; the site holds none. The landing page is static. The account pages (`/sign-in`, `/dashboard`, `/dashboard/licenses`, `/dashboard/billing`, `/dashboard/api`, `/account`) use Better Auth and read Postgres through the `HYPERDRIVE` binding. Locally everything runs on this machine: a Postgres and a Mailpit container per checkout, an OAuth mock that stands in for GitHub and Google, and a billing mock (`tools/billing-mock`) that speaks the parts of Polar's API and Resend's `/emails` that convt-billing calls, serves Polar-like checkout and portal pages, and signs webhooks with Polar's real scheme. No real email, OAuth app, payment account or database is ever used, and convt-billing signs keys with the local dev key in `.convt-dev/license.key`. ## Doctor ```sh docker info --format '{{.ServerVersion}}' # Docker must run; no sudo needed bash scripts/db.sh status # this checkout's containers and ports ss -ltnp 'sport = :3000' # who owns the default web port ``` `scripts/db.sh` names its containers `convt-pg-<hash>` and `convt-mail-<hash>` after a hash of the checkout path and labels them `convt.checkout=<path>`, so other checkouts and other projects' Postgres containers are never touched. Ports are random on 127.0.0.1; `.convt-dev/services.env` (mode 0600, gitignored) records them with generated passwords and `BETTER_AUTH_SECRET`. ## Launch ```sh work=$(mktemp -d /tmp/convt-web.XXXXXX) setsid bun run dev:web >"$work/dev.log" 2>&1 </dev/null & sleep 1; ps -o pgid= -p $! | tr -d ' ' >"$work/pgid" for _ in $(seq 90); do grep -q 'dev-web: http' "$work/dev.log" && break; sleep 1; done origin=$(grep -o 'dev-web: http://localhost:[0-9]*' "$work/dev.log" | cut -d' ' -f2) curl -fsS "$origin/api/auth/ok" # {"ok":true} bun run db:seed # fixture accounts; safe to repeat ``` `bun run dev:web` runs `scripts/dev-web.sh`: `db.sh up`, migrations, the OAuth mock on `OAUTH_MOCK_PORT`, the billing mock on `BILLING_MOCK_PORT` (delivering webhooks to the site's `/webhooks/polar`), `apps/web/.dev.vars` and `apps/billing/.dev.vars` from `services.env`, then Vite on 3000 or the next free port with `BETTER_AUTH_URL` set to match. convt-billing runs inside Vite as an auxiliary Worker; outside production the site forwards `/webhooks/*` and `/__billing/*` to it, because an auxiliary Worker has no port of its own. After seeding, mirror the fixtures into the billing mock with `bun tools/billing-mock/src/preload.ts` (it is in-memory, so repeat it after every restart). Readiness is `/api/auth/ok` returning 200. Server-side errors appear in `dev.log`, not the browser console. `setsid` puts Vite and the mock in one process group so cleanup can stop exactly those. For a production-like check, build and serve the Worker with Wrangler. Pass the database through the Hyperdrive variable and point `BETTER_AUTH_URL` at Wrangler's port: ```sh set -a; . .convt-dev/services.env; set +a bun run build cd apps/web && CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE=$DATABASE_URL \ setsid bunx wrangler dev --port 8788 --ip 127.0.0.1 --var BETTER_AUTH_URL:http://localhost:8788 & ``` It reads the other values from `.dev.vars`, which the build copies into `dist/server`. Never run `bun run deploy` or `wrangler deploy` without the user's explicit authorization. ## Sign in as a fixture All fixtures are on the reserved `.test` domain and exist only in local databases: | Email | Shows | | ----------------------- | ------------------------------------------------------------------------------------ | | `new@convt.test` | Signed in, bought nothing. | | `trial@convt.test` | Pro monthly trial ending in 3 days, trial key. | | `desktop@convt.test` | Desktop order, key, invoice and one Mac. | | `pro@convt.test` | Pro yearly, older Desktop key, two Macs, API with two keys and usage, GitHub linked. | | `lapsed@convt.test` | Pro ended last month, last key, a failed invoice. | | `api@convt.test` | API only: one key, usage, six failed jobs. | | `unclaimed@convt.test` | No account: a Desktop purchase that signing up with this address claims. | | `refunded@convt.test` | A Desktop purchase refunded in full: the key shows REFUNDED. | | `pastdue@convt.test` | Pro monthly whose renewal failed: past due, last month's key. | | `disputed@convt.test` | A Desktop purchase with a lost chargeback: the key shows DISPUTED. | | `apipending@convt.test` | API enrollment waiting for a card (pending). | `trial@` has no key (a trial is not paid coverage); a database seeded before P7 keeps its old trial key, because licenses are never deleted. Every subscription fixture has payment coverage and a `billing_customers` row with mock ids. Sign in at `/sign-in` with the email, then read the code from Mailpit (its web UI is `$MAILPIT_URL`): ```sh curl -fsS "$MAILPIT_URL/api/v1/search?query=to:pro@convt.test" \ | python3 -c 'import json,re,sys; print(re.search(r"\d{6}", json.load(sys.stdin)["messages"][0]["Subject"]).group())' ``` Each address may receive 3 codes per 15 minutes and each IP 10 per hour; every local request comes from the same IP. To clear the limits in this checkout's own database: `bash scripts/db.sh psql owner -c 'delete from otp_send_limits; delete from rate_limits'`. GitHub and Google buttons go to the OAuth mock's authorize page, which lists its identities: `google-gmail` and `google-workspace` (verified addresses), `google-thirdparty` and `google-unverified` (must confirm by code), `github-verified`, `github-public-differs`, `github-no-email`, `github-pro` (signs in to `pro@convt.test`), and `error` (access denied). Append `&identity=<name>` to the authorize URL to skip the page and `&email=<address>` to sign up with another address. ## Drive and evidence Use headless `agent-browser` for UI verification, with a named session and its own persistent profile. Put both `--session` and `--profile` on every invocation. Never use a personal browser profile. UI work includes opening the real page and inspecting desktop and mobile screenshots in both themes. The repeatable checks are scripts in `apps/web/e2e`, each printing PASS or FAIL lines and saving screenshots: ```sh cd apps/web E2E_SESSION=convt-web E2E_SHOTS=$work/shots bash e2e/sign-in.sh # code, emailed link, OAuth identities, verify-email E2E_SESSION=convt-web E2E_SHOTS=$work/shots bash e2e/fixtures.sh # every fixture, 5 pages, 1280 and 390 px, light and dark E2E_SESSION=convt-web E2E_SHOTS=$work/shots bash e2e/account.sh # activate, copy key, rename, change email, connect, sign out E2E_SESSION=convt-web E2E_SHOTS=$work/shots bash e2e/billing.sh # checkout, keys, trial, switch, refund, API, emails, deletion ``` They read the site origin from `apps/web/.dev.vars`; set `E2E_URL` to test Wrangler instead. `billing.sh` drives the mock's hosted checkout and its `/admin/*` endpoints (end a trial, renew, fail a card, refund, change settings), runs crons through `$E2E_URL/__billing/scheduled?cron=...`, and screenshots each billing screen and the four emails (Mailpit's `/view/<id>.html`) in both themes. Run it once per fresh `dev:web`: it changes mock settings and deletes an account. `account.sh` calls a source module directly to prove `getLicenseKey` refuses another user's license, which only works under Vite. A layout change needs desktop and 390 px screenshots in both themes; look at each one with the Read tool. Activate opens `convt://activate?key=<token>` through a temporary link. `account.sh` stubs `HTMLAnchorElement.prototype.click` to check the URL, because Chrome lets no page script stub `location`. Opening the link in the real desktop app is a GUI check (see test-convt-desktop): register the handler with `integrations/linux/install.py` and use `CONVT_LICENSE_STORE=file` with `HOME` and the XDG directories in a `mktemp` directory. Seeded keys are signed with `.convt-dev/license.key`, whose public half local builds embed. ## Desktop sign-in `/device` is the page the desktop app opens to sign in (P8). It needs a signed-in, verified session, so a signed-out visit goes through `/sign-in` and comes back. Approve stores a five-minute one-time code in `verifications` and opens `convt://auth?state=...&code=...`; Cancel opens `...&error=access_denied`. The app then calls `POST /api/device/token` (code and verifier), `POST /api/device/license` (bearer device token; returns the Pro key from convt-billing's `currentProKey`) and `POST /api/device/sign-out`. These routes use no cookies and skip the Origin check; the logic and limits are in `src/server/device-auth.ts`. `test/integration/device.test.ts` covers the flow, one-use codes, revocation, cross-account isolation and rate limits. A full run with the real app is in `test-convt-desktop`, "Sign-in and renewal". Signed-in devices appear under Macs on the dashboard, where Sign out revokes their token. ## Billing locally - The mock's admin API: `curl -X POST $BILLING_MOCK_URL/admin/<name> -d '{...}'` with `state` (GET), `clock` (`advanceDays`), `trial-end`, `renew`, `card` (`decline`), `retry-payment`, `refund` (`order_id`, `amount`), `dispute` (`open`, then `lost`/`won`/`prevented`), `settings` (`allow_multiple_subscriptions`), `product` (price drift), `webhooks` (`mode` auto or hold, `dropNext`, `duplicate`, `delayMs`, `reorder`, `forgeNext`, `scheme` standard or legacy, `url`), `flush`, `resend-fault` (`timeout`, `delay`, `5xx`), `complete-checkout`. Polar sends no dispute webhook; disputes arrive through the reconciler (`cron=*/15 * * * *`). - Crons: `curl "$origin/__billing/scheduled?cron=*+*+*+*+*"` drains the outbox and advances deletions; `*%2F15+*+*+*+*` runs the reconciler; `17+3+*+*+*` the daily check and digest (to `alerts@convt.test` in Mailpit). - `bun run billing:outbox list` shows ambiguous and dead emails; `resolve <id> sent|resend` records Leo's decision. - The second dev mode is `cd apps/billing && CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE_BILLING=$BILLING_DATABASE_URL bunx wrangler dev --port 8790 --test-scheduled`, with the mock's webhook URL set to it (`admin/webhooks` `url`). The site under `wrangler dev` reaches it through the dev registry; on a machine short of file watches (EMFILE in the log) that registry never finishes starting. - Billing Worker settings and guards: `packages/billing/src/env.ts`. In production it refuses loopback provider or mail URLs and a signing key that does not match `LICENSE_PUBLIC_KEY` or is a dev key. ## Tests ```sh cd apps/web && bun test test/unit # views, redirects, Origin, redactor, env, table shape bun run --cwd apps/web test:integration # Better Auth in process against a throwaway Postgres bun run db:ci # schema drift, down files, all integration tests, convt-server cd packages/billing && bun test test/unit && bun run test:integration # verifier, catalog, guards; ingest, keys, outbox, reconciler, deletion cd tools/billing-mock && bun test # the mock through the real SDK client, validateEvent, Resend idempotency cd packages/mail && bun test # template snapshots and escaping, Resend outcomes ``` The billing integration tests run the mock in process through the real Polar SDK client, as `convt_billing` against a disposable database, with an injected clock; one test builds `convt-cli` and activates an issued key with `CONVT_LICENSE_STORE=file` in a temporary HOME. A change to an email template changes its snapshot: bump `templateVersion` in `packages/mail/src/templates.ts` with it. Integration tests and `db:ci` start their own labeled tmpfs Postgres and remove it by id on exit. ## Preview for another device Secure cookies are off in development, but the sign-in origin must be the one the device uses. Start with `PUBLIC_ORIGIN=http://<tailnet-host>:<port> PORT=<port> MOCK_HOST=0.0.0.0 MOCK_PUBLIC_URL=http://<tailnet-host>:<mock-port> bun run dev:web`, which binds Vite to all interfaces and sets `BETTER_AUTH_URL` and the mock's authorize URL to those addresses. This path was not exercised when P6 was verified, so check sign-in end to end the first time. Verify the URL from the device that will open it, and report which devices actually loaded the page. Read the remote-preview skill before starting or sharing a preview. Bind to the verified Tailscale address and report which devices actually loaded the page. ## Checks ```sh bun run check && bun run check-types && bun run build ``` ## Cleanup `kill -- -"$(cat "$work/pgid")"` stops Vite and the OAuth mock you started (only that process group). Close browser sessions with `agent-browser --session <name> close`. The containers stay for reuse; `bun run db:down` removes this checkout's containers and its database volume (after the ownership guard), and `bash scripts/db.sh prune` removes those of checkouts that no longer exist. ## P9 cloud converter and API keys Read test-convt-server and `docs/p9-cloud-plan.md` to start the API, MinIO and sandbox worker. Configure `CONVT_API_URL` and the shared `CONVT_WEB_TOKEN_SECRET` in the local web Worker. Use the task session `convt-p9` and profile `~/.agent-browser/profiles/convt-p9`, on every command. Check `/dashboard/api`: create a named key, inspect the shown-once field, dismiss it, reload, revoke it and verify a revoked key fails authentication. Inspect spend, reservations, cap reached and not-enrolled states. Check `/dashboard/api/convert`: pick a real file, choose a supported target, observe upload and job progress, download and inspect the result. Test malformed input, cancellation, an unavailable cancel endpoint and the retry button, the 2 GB file guard, exhausted monthly allowance, unpaid and trial states. Temporary reservations used to reach a limit must be cancelled afterward; restore any temporary cap only if it still has the value the test set. Never rewrite settled usage to stage a screenshot. Capture desktop and 390 px screenshots in light and dark, inspect each image, and verify no horizontal overflow. Check keyboard focus and accessible names. Save P9 evidence in `/home/leo/projects/convt-ui-brief/screens/p9/`. If Vite hits EMFILE, restart only the owned process with `CHOKIDAR_USEPOLLING=1`. Hot reload can reset a selected file; finish source edits before the final browser run. Production Worker, Railway Buckets CORS and real payment enrollment remain separate launch checks. ## Billing security regressions Run `bun test apps/billing/test` to verify the public webhook body's streaming reader. A body without Content-Length must stop and cancel when it crosses 256 KiB, exact-limit raw bytes must survive unchanged, and non-POST requests must consume no body. The billing integration deletion case includes Pro usage and pending API usage: only the API fact for the subscription being revoked may delay deletion. `bun audit` and the database dependency test must reject vulnerable esbuild versions; `bun run db:ci` checks the overridden loader against real migration tooling.
عرض على GitHub