// batch13-webhook-downgrade.mjs — the last item on the billing Drive list. // // 6510c16 introduced a gap between "listed" and "delivered": dispatch caps a // user's webhooks to their CURRENT allowance, while the settings page keeps // showing every webhook they created. This drives the gap: create three while // entitled to ten, cancel the subscription through Stripe's real portal, and // confirm all three are still listed under an allowance of one. // // The delivery half is not observable here — it fires from DispatchForScan and // the dev stack runs no scanner. It is unit-covered by 33c2321's own // dispatch_entitlement_test.go. import { open, reporter, APPVIEW } from './lib.mjs'; const { record, summarize } = reporter(); const ctx = await open(); const page = ctx.pages()[0] ?? (await ctx.newPage()); const text = () => page.evaluate(() => document.body.innerText); const allowance = (s) => (s.match(/(\d+)\s*\/\s*(\d+)\s*webhooks configured/) ?? []).slice(1, 3); await page.goto(`${APPVIEW}/settings/webhooks`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2000); let hooks = await text(); console.log('before:', allowance(hooks).join(' of '), 'configured\n'); // Create three webhooks through the form the user would use. for (const n of [1, 2, 3]) { const url = `https://example.invalid/hook-${n}`; const already = (await text()).includes(url); if (already) { console.log(`hook-${n} already present`); continue; } // Scope to the webhook form: a bare button[type=submit] also matches the // nav's hidden logout button, which is the first match in the DOM. const form = page.locator('form[hx-post="/api/webhooks"]'); await form.locator('input[name="url"]').fill(url); await form.locator('button[type="submit"]').click(); await page.waitForTimeout(2000); console.log(`created hook-${n}`); } await page.goto(`${APPVIEW}/settings/webhooks`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(1500); hooks = await text(); const [have, cap] = allowance(hooks); console.log(`after creating: ${have} of ${cap} configured\n`); record( 'three webhooks exist while entitled', Number(have) >= 3, `only ${have} present; the downgrade check needs three`, ); record('the allowance is the paid one', Number(cap) >= 3, `cap is ${cap}, expected the Pro allowance`); console.log(hooks.slice(0, 1400)); summarize(); console.log('\nLeaving the browser open for the portal step.'); await new Promise(() => {});