11import { beforeEach , describe , expect , it , vi } from 'vitest'
22
3+ import type { SubscriptionInfo } from '@/composables/billing/types'
4+ import type {
5+ BillingSubscriptionStatus ,
6+ TeamCreditStops ,
7+ TeamCreditStopSummary
8+ } from '@/platform/workspace/api/workspaceApi'
39import { savePendingSubscriptionCheckout } from '@/platform/workspace/utils/pendingSubscriptionCheckout'
410
511import { useSubscriptionDialog } from './useSubscriptionDialog'
@@ -24,7 +30,18 @@ const mockEmbeddedCheckoutEnabled = vi.hoisted(() => ({ value: false }))
2430const mockActiveWorkspaceId = vi . hoisted ( ( ) => ( { value : 'workspace-1' } ) )
2531const mockStartOperation = vi . hoisted ( ( ) => vi . fn ( ) )
2632const mockFetchPlans = vi . hoisted ( ( ) => vi . fn ( ) )
27- const mockTeamCreditStops = vi . hoisted ( ( ) => ( { value : null } ) )
33+ const mockTeamCreditStops = vi . hoisted ( ( ) => ( {
34+ value : null as TeamCreditStops | null
35+ } ) )
36+ const mockCurrentTeamCreditStop = vi . hoisted ( ( ) => ( {
37+ value : null as TeamCreditStopSummary | null
38+ } ) )
39+ const mockSubscription = vi . hoisted ( ( ) => ( {
40+ value : null as Pick < SubscriptionInfo , 'duration' > | null
41+ } ) )
42+ const mockSubscriptionStatus = vi . hoisted ( ( ) => ( {
43+ value : null as BillingSubscriptionStatus | null
44+ } ) )
2845
2946vi . mock ( 'vue' , async ( importOriginal ) => {
3047 const actual = await importOriginal ( )
@@ -101,7 +118,10 @@ vi.mock('@/composables/billing/useBillingContext', () => ({
101118 currentPlanSlug : mockCurrentPlanSlug ,
102119 tier : mockTier ,
103120 fetchPlans : mockFetchPlans ,
104- teamCreditStops : mockTeamCreditStops
121+ teamCreditStops : mockTeamCreditStops ,
122+ currentTeamCreditStop : mockCurrentTeamCreditStop ,
123+ subscription : mockSubscription ,
124+ subscriptionStatus : mockSubscriptionStatus
105125 } )
106126} ) )
107127
@@ -147,6 +167,10 @@ describe('useSubscriptionDialog', () => {
147167 mockActiveWorkspaceId . value = 'workspace-1'
148168 mockStartOperation . mockResolvedValue ( { status : 'succeeded' } )
149169 mockFetchPlans . mockResolvedValue ( undefined )
170+ mockTeamCreditStops . value = null
171+ mockCurrentTeamCreditStop . value = null
172+ mockSubscription . value = null
173+ mockSubscriptionStatus . value = null
150174 sessionStorage . clear ( )
151175 } )
152176
@@ -697,5 +721,91 @@ describe('useSubscriptionDialog', () => {
697721 sessionStorage . getItem ( 'comfy:pending-subscription-checkout' )
698722 ) . toBeNull ( )
699723 } )
724+
725+ it ( 'restores a Team plan change from fresh catalog and subscription state' , async ( ) => {
726+ mockShouldUseWorkspaceBilling . value = true
727+ mockStartOperation . mockResolvedValueOnce ( { status : 'failed' } )
728+ mockTeamCreditStops . value = {
729+ default_stop_index : 0 ,
730+ stops : [
731+ {
732+ id : 'team_700' ,
733+ credits : 147_700 ,
734+ monthly : {
735+ list_price_cents : 70_000 ,
736+ price_cents : 66_500
737+ } ,
738+ yearly : {
739+ list_price_cents : 70_000 ,
740+ price_cents : 63_000
741+ }
742+ }
743+ ]
744+ }
745+ mockCurrentTeamCreditStop . value = {
746+ id : 'team_400' ,
747+ stop_usd : 400 ,
748+ credits_monthly : 84_400
749+ }
750+ mockSubscription . value = { duration : 'MONTHLY' }
751+ mockSubscriptionStatus . value = 'active'
752+ savePendingSubscriptionCheckout ( {
753+ operationId : 'op-team-change' ,
754+ workspaceId : 'workspace-1' ,
755+ selection : {
756+ planMode : 'team' ,
757+ teamCreditStopId : 'team_700' ,
758+ billingCycle : 'yearly'
759+ } ,
760+ attemptedAt : Date . now ( )
761+ } )
762+
763+ const { resumePendingPricingFlow } = useSubscriptionDialog ( )
764+ await resumePendingPricingFlow ( )
765+
766+ expect ( mockShowLayoutDialog ) . toHaveBeenCalledWith (
767+ expect . objectContaining ( {
768+ props : expect . objectContaining ( {
769+ initialCheckout : {
770+ planMode : 'team' ,
771+ stop : {
772+ id : 'team_700' ,
773+ credits : 147_700 ,
774+ usd : 700 ,
775+ discountedUsd : 630
776+ } ,
777+ billingCycle : 'yearly' ,
778+ isChange : true
779+ }
780+ } )
781+ } )
782+ )
783+ } )
784+
785+ it ( 'falls back to Team pricing when the stop catalog is unavailable' , async ( ) => {
786+ mockShouldUseWorkspaceBilling . value = true
787+ mockStartOperation . mockResolvedValueOnce ( { status : 'failed' } )
788+ savePendingSubscriptionCheckout ( {
789+ operationId : 'op-team-catalog-failure' ,
790+ workspaceId : 'workspace-1' ,
791+ selection : {
792+ planMode : 'team' ,
793+ teamCreditStopId : 'team_700' ,
794+ billingCycle : 'yearly'
795+ } ,
796+ attemptedAt : Date . now ( )
797+ } )
798+
799+ const { resumePendingPricingFlow } = useSubscriptionDialog ( )
800+ await resumePendingPricingFlow ( )
801+
802+ expect ( mockShowLayoutDialog ) . toHaveBeenCalledWith (
803+ expect . objectContaining ( {
804+ props : expect . objectContaining ( { initialPlanMode : 'team' } )
805+ } )
806+ )
807+ const props = mockShowLayoutDialog . mock . calls [ 0 ] [ 0 ] . props
808+ expect ( props . initialCheckout ) . toBeUndefined ( )
809+ } )
700810 } )
701811} )
0 commit comments