Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions browser_tests/fixtures/data/cloudWorkspace.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { BillingStatusResponse as IngestBillingStatusResponse } from '@comfyorg/ingest-types'

import type {
BillingStatusResponse,
Member,
Plan,
WorkspaceWithRole
Expand Down Expand Up @@ -67,16 +68,29 @@ export const DEFAULT_TEAM_MEMBERS: Member[] = [
MEMBER_JOHN
]

export const TEAM_BILLING_STATUS: BillingStatusResponse = {
export const TEAM_BILLING_STATUS = {
is_active: true,
subscription_status: 'active',
subscription_tier: 'PRO',
subscription_duration: 'MONTHLY',
plan_slug: 'pro-monthly',
billing_status: 'paid',
has_funds: true,
renewal_date: '2099-02-20T00:00:00Z'
}
renewal_date: '2099-02-20T00:00:00Z',
team_credit_stop: null
} satisfies IngestBillingStatusResponse

export const ENDED_STANDARD_BILLING_STATUS = {
billing_rail: 'stripe',
billing_status: 'inactive',
has_funds: true,
is_active: false,
plan_slug: 'standard-monthly',
subscription_duration: 'MONTHLY',
subscription_status: 'ended',
subscription_tier: 'STANDARD',
team_credit_stop: null
} satisfies IngestBillingStatusResponse & { billing_rail: 'stripe' }

// `max_seats > 1` on the current plan is what flips `isOnTeamPlan`, which gates
// the whole role-management UI.
Expand Down
33 changes: 27 additions & 6 deletions browser_tests/fixtures/helpers/CloudWorkspaceMockHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Page, Route } from '@playwright/test'
import type { BillingStatusResponse } from '@comfyorg/ingest-types'

import type { Member } from '@/platform/workspace/api/workspaceApi'
import type { Member, Plan } from '@/platform/workspace/api/workspaceApi'

import { mockSystemStats } from '@e2e/fixtures/data/systemStats'
import {
Expand Down Expand Up @@ -41,9 +42,10 @@ export class CloudWorkspaceMockHelper {
constructor(private readonly page: Page) {}

async setup(
members: Member[] = DEFAULT_TEAM_MEMBERS
members: Member[] = DEFAULT_TEAM_MEMBERS,
billingStatus: BillingStatusResponse = TEAM_BILLING_STATUS
): Promise<MemberMockState> {
const state = await this.mockBoot(members)
const state = await this.mockBoot(members, billingStatus)
await new CloudAuthHelper(this.page).mockAuth()
await this.page.addInitScript(() => {
localStorage.setItem('Comfy.userId', 'test-user-e2e')
Expand All @@ -52,7 +54,10 @@ export class CloudWorkspaceMockHelper {
return state
}

private async mockBoot(members: Member[]): Promise<MemberMockState> {
private async mockBoot(
members: Member[],
billingStatus: BillingStatusResponse
): Promise<MemberMockState> {
const state: MemberMockState = {
members: members.map((m) => ({ ...m })),
patches: []
Expand Down Expand Up @@ -125,7 +130,7 @@ export class CloudWorkspaceMockHelper {
)

await page.route('**/api/billing/status', (r) =>
r.fulfill(jsonRoute(TEAM_BILLING_STATUS))
r.fulfill(jsonRoute(billingStatus))
)
await page.route('**/api/billing/balance', (r) =>
r.fulfill(
Expand All @@ -138,9 +143,25 @@ export class CloudWorkspaceMockHelper {
})
)
)
const currentPlan: Plan =
billingStatus.plan_slug && billingStatus.plan_slug !== TEAM_PRO_PLAN.slug
? {
...TEAM_PRO_PLAN,
slug: billingStatus.plan_slug,
tier:
billingStatus.subscription_tier === 'TEAM'
? TEAM_PRO_PLAN.tier
: (billingStatus.subscription_tier ?? TEAM_PRO_PLAN.tier),
duration:
billingStatus.subscription_duration ?? TEAM_PRO_PLAN.duration
}
: TEAM_PRO_PLAN
await page.route('**/api/billing/plans', (r) =>
r.fulfill(
jsonRoute({ current_plan_slug: 'pro-monthly', plans: [TEAM_PRO_PLAN] })
jsonRoute({
current_plan_slug: currentPlan.slug,
plans: [currentPlan]
})
)
)

Expand Down
53 changes: 53 additions & 0 deletions browser_tests/tests/dialogs/endedSubscription.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { expect } from '@playwright/test'

import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
import {
DEFAULT_TEAM_MEMBERS,
ENDED_STANDARD_BILLING_STATUS
} from '@e2e/fixtures/data/cloudWorkspace'
import { CloudWorkspaceMockHelper } from '@e2e/fixtures/helpers/CloudWorkspaceMockHelper'
import { TestIds } from '@e2e/fixtures/selectors'

test.describe('Ended workspace subscription', { tag: '@cloud' }, () => {
test.describe.configure({ timeout: 60_000 })

test.beforeEach(async ({ page }) => {
await new CloudWorkspaceMockHelper(page).setup(
DEFAULT_TEAM_MEMBERS,
ENDED_STANDARD_BILLING_STATUS
)
await page.goto(process.env.PLAYWRIGHT_TEST_URL || 'http://localhost:8188')
await page.waitForFunction(() => !!window.app?.extensionManager, null, {
timeout: 45_000
})

await page
.getByRole('button', { name: /^Settings/ })
.first()
.click()
const dialog = page.getByTestId(TestIds.dialogs.settings)
await expect(dialog).toBeVisible()
await dialog
.locator('nav')
.getByRole('button', { name: 'Workspace', exact: true })
.click()
})

test('shows subscribe prompt instead of stale paid plan metadata', async ({
page
}) => {
const content = page.getByTestId(TestIds.dialogs.settings).getByRole('main')

await expect(
content.getByRole('heading', {
name: 'This workspace is not on a subscription'
})
).toBeVisible()
await expect(
content.getByRole('button', { name: 'Subscribe Now' })
).toBeVisible()
await expect(
content.getByRole('heading', { name: 'Standard' })
).toHaveCount(0)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { SubscriptionInfo } from '@/composables/billing/types'
import enMessages from '@/locales/en/main.json'
import type {
BillingStatus,
BillingSubscriptionStatus,
CurrentTeamCreditStop,
TeamCreditStops
} from '@/platform/workspace/api/workspaceApi'
Expand Down Expand Up @@ -46,7 +47,7 @@ const teamCreditStops: TeamCreditStops = {
]
}

const mockSubscriptionStatus = ref<'active' | 'canceled'>('active')
const mockSubscriptionStatus = ref<BillingSubscriptionStatus>('active')
const mockBillingStatus = ref<BillingStatus>('paid')
const mockSubscriptionDuration = ref<'MONTHLY' | 'ANNUAL'>('MONTHLY')
const mockHasSubscription = ref(true)
Expand Down Expand Up @@ -131,6 +132,7 @@ vi.mock('@/composables/billing/useBillingContext', () => ({
isActiveSubscription: computed(() => mockIsActiveSubscription.value),
isFreeTier: computed(() => false),
billingStatus: mockBillingStatus,
subscriptionStatus: mockSubscriptionStatus,
subscription: mockSubscription,
teamCreditStops: mockTeamCreditStops,
currentTeamCreditStop: mockCurrentTeamCreditStop,
Expand Down Expand Up @@ -393,6 +395,23 @@ describe('SubscriptionPanelContentWorkspace', () => {
expect(screen.getByText('Invite members')).toBeInTheDocument()
})

it('shows subscribe prompt for an ended subscription despite stale paid plan metadata', () => {
mockSubscriptionStatus.value = 'ended'
renderComponent()

expect(
screen.getByRole('heading', {
name: 'This workspace is not on a subscription'
})
).toBeInTheDocument()
expect(
screen.getByRole('button', { name: 'Subscribe Now' })
).toBeInTheDocument()
expect(
screen.queryByRole('heading', { name: 'Team' })
).not.toBeInTheDocument()
})

it('reactivates a cancelled plan as the original owner, keeping Manage billing', async () => {
const user = userEvent.setup()
mockSubscriptionStatus.value = 'canceled'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ const {
isFreeTier: isFreeTierPlan,
subscription,
billingStatus,
subscriptionStatus,
isLoading,
error,
showSubscriptionDialog,
Expand All @@ -370,6 +371,7 @@ const isTerminalPersonalSubscription = computed(
// stays active until its end date, so it keeps the subscribed treatment.
const showSubscribePrompt = computed(() => {
if (!permissions.value.canManageSubscription) return false
if (subscriptionStatus.value === 'ended') return true
if (isTerminalPersonalSubscription.value) return true
if (isTeamPlanCancelled.value) return false
if (isInPersonalWorkspace.value) {
Expand Down
Loading