-
Notifications
You must be signed in to change notification settings - Fork 673
fix: restart timed-out billing operations #14396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -265,6 +265,22 @@ const PAYMENT_METHOD_REQUIRED_RESPONSE = { | |
| payment_method_url: 'https://pay.test/method' | ||
| } satisfies SubscribeResponse | ||
|
|
||
| const RETRIED_SUBSCRIPTION_OPERATION_ID = 'retried-subscription' | ||
| const RETRIED_SUBSCRIPTION_ACTION_URL = | ||
| 'https://verify.example/retried-subscription' | ||
|
|
||
| const RETRIED_SUBSCRIPTION_RESPONSE = { | ||
| billing_op_id: RETRIED_SUBSCRIPTION_OPERATION_ID, | ||
| status: 'needs_payment_method', | ||
| payment_method_url: 'https://pay.test/retried-subscription' | ||
| } satisfies SubscribeResponse | ||
|
|
||
| const RETRIED_SUBSCRIPTION_OPERATION = { | ||
| id: RETRIED_SUBSCRIPTION_OPERATION_ID, | ||
| status: 'pending', | ||
| started_at: '2026-07-30T00:00:00Z' | ||
| } satisfies BillingOpStatusResponse | ||
|
|
||
| const UNEXPECTED_OPERATION_RESPONSE = { | ||
| id: PAYMENT_METHOD_REQUIRED_RESPONSE.billing_op_id, | ||
| status: 'failed', | ||
|
|
@@ -574,6 +590,80 @@ test.describe('Pricing table deep link', { tag: '@cloud' }, () => { | |
| await expect(page).not.toHaveURL(/[?&](pricing|cycle)=/) | ||
| }) | ||
|
|
||
| test('restores pending checkout when retrying a timed-out operation', async ({ | ||
| page | ||
| }) => { | ||
| const subscribeRequests: Request[] = [] | ||
| const operationPollRequests: Request[] = [] | ||
| await page.addInitScript(() => { | ||
| window.open = () => window | ||
| }) | ||
| await setupCloudApp(page, workspace('personal', 'owner'), []) | ||
| await page.route('**/api/billing/status', (route) => | ||
| route.fulfill(jsonRoute(LEGACY_ACTIVE_STANDARD_STATUS)) | ||
| ) | ||
| await page.route('**/api/billing/plans', (route) => | ||
| route.fulfill( | ||
| jsonRoute({ | ||
| plans: [CREATOR_ANNUAL_PLAN] | ||
| } satisfies BillingPlansResponse) | ||
| ) | ||
| ) | ||
| await page.route('**/api/billing/preview-subscribe', (route) => | ||
| route.fulfill(jsonRoute(NEW_CREATOR_SUBSCRIPTION)) | ||
| ) | ||
| await page.route('**/api/billing/subscribe', (route) => { | ||
| subscribeRequests.push(route.request()) | ||
| return route.fulfill(jsonRoute(RETRIED_SUBSCRIPTION_RESPONSE)) | ||
| }) | ||
| await page.route( | ||
| `**/api/billing/ops/${RETRIED_SUBSCRIPTION_OPERATION_ID}`, | ||
| (route) => { | ||
| operationPollRequests.push(route.request()) | ||
| return route.fulfill( | ||
| jsonRoute({ | ||
| ...RETRIED_SUBSCRIPTION_OPERATION, | ||
| ...(subscribeRequests.length > 1 && { | ||
| action_url: RETRIED_SUBSCRIPTION_ACTION_URL | ||
| }) | ||
| } satisfies BillingOpStatusResponse) | ||
| ) | ||
| } | ||
| ) | ||
|
|
||
| await page.goto(`${APP_URL}/?pricing=creator&cycle=yearly`) | ||
|
|
||
| const subscribeButton = page.getByRole('button', { | ||
| name: 'Subscribe to Creator' | ||
| }) | ||
| const backButton = page.getByRole('button', { name: 'Back', exact: true }) | ||
| await cloudAppExpect(subscribeButton).toBeVisible() | ||
| await page.clock.install({ time: new Date('2026-07-30T00:00:00Z') }) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Severity: Minor What: Why this is an issue: Workspace boot has already used Proposed Fix: Move |
||
| await subscribeButton.click() | ||
| await expect.poll(() => subscribeRequests.length).toBe(1) | ||
| await expect.poll(() => operationPollRequests.length).toBeGreaterThan(0) | ||
| await expect(backButton).toBeDisabled() | ||
|
|
||
| await page.clock.fastForward(5 * 60_000 + 1) | ||
|
|
||
| await expect(backButton).toBeEnabled() | ||
| await expect( | ||
| page.getByText('Subscription verification timed out', { exact: true }) | ||
| ).toBeVisible() | ||
| const pollCountAfterTimeout = operationPollRequests.length | ||
|
|
||
| await subscribeButton.click() | ||
|
|
||
| await expect.poll(() => subscribeRequests.length).toBe(2) | ||
| await expect(backButton).toBeDisabled() | ||
| await expect | ||
| .poll(() => operationPollRequests.length) | ||
| .toBeGreaterThan(pollCountAfterTimeout) | ||
| await expect( | ||
| page.getByRole('button', { name: 'Complete verification' }) | ||
| ).toBeVisible() | ||
| }) | ||
|
|
||
| test('cleans orphaned pricing params without opening the table', async ({ | ||
| page | ||
| }) => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Severity: Minor
What: This test declares its request trackers and performs its init script, cloud setup, and route registrations inside
test().Why this is an issue: The binding browser-test guidance requires mock setup and fixture arrangement in
beforeEachor fixtures, with test bodies limited to act/assert. This couples stateful setup to the scenario and violates the repository’s required test structure.Proposed Fix: Put this scenario in a nested
test.describeand move the trackers and route setup intotest.beforeEachor a custom fixture.