11import { billingCoreMock , billingCoreMockFns } from '@sim/testing/mocks/billing-core.mock'
22import { billingOutboxHandlersMock } from '@sim/testing/mocks/billing-outbox-handlers.mock'
33import { billingPlanMock , billingPlanMockFns } from '@sim/testing/mocks/billing-plan.mock'
4+ import {
5+ billingSubscriptionSyncMock ,
6+ billingSubscriptionSyncMockFns ,
7+ } from '@sim/testing/mocks/billing-subscription-sync.mock'
48import { dbChainMockFns , resetDbChainMock } from '@sim/testing/mocks/database.mock'
59import {
610 organizationMembershipMock ,
711 organizationMembershipMockFns ,
812} from '@sim/testing/mocks/organization-membership.mock'
9- import { outboxServiceMock , outboxServiceMockFns } from '@sim/testing/mocks/outbox-service.mock'
13+ import { outboxServiceMock } from '@sim/testing/mocks/outbox-service.mock'
1014import { afterAll , beforeEach , describe , expect , it , vi } from 'vitest'
1115
1216const {
@@ -41,6 +45,8 @@ vi.mock('@/lib/billing/plans', () => ({
4145
4246vi . mock ( '@/lib/core/outbox/service' , ( ) => outboxServiceMock )
4347
48+ vi . mock ( '@/lib/billing/webhooks/subscription-sync' , ( ) => billingSubscriptionSyncMock )
49+
4450vi . mock ( '@/lib/billing/webhooks/outbox-handlers' , ( ) => billingOutboxHandlersMock )
4551
4652import { ensureTeamOrganizationForAcceptance } from '@/lib/billing/organizations/provision-seat'
@@ -49,15 +55,21 @@ const { mockAcquireOrganizationMutationLock } = organizationMembershipMockFns
4955const mockGetOrganizationSubscription = billingCoreMockFns . mockGetOrganizationSubscription
5056const mockGetHighestPriorityPersonalSubscription =
5157 billingPlanMockFns . mockGetHighestPriorityPersonalSubscription
52- const enqueueMock = outboxServiceMockFns . mockEnqueueOutboxEvent
58+ const { mockEnqueueSubscriptionSeatsSync, mockEnqueueCancelAtPeriodEndSync } =
59+ billingSubscriptionSyncMockFns
5360
5461function testExecutor ( onUpdate : ( ) => void = ( ) => { } ) {
5562 return {
5663 update : ( ) => ( {
5764 set : ( values : Record < string , unknown > ) => {
5865 onUpdate ( )
5966 updateCalls . value . push ( values )
60- return { where : ( ) => Promise . resolve ( [ ] ) }
67+ return {
68+ where : ( ) =>
69+ Object . assign ( Promise . resolve ( [ ] ) , {
70+ returning : ( ) => Promise . resolve ( [ { seats : 1 } ] ) ,
71+ } ) ,
72+ }
6173 } ,
6274 } ) ,
6375 } as never
@@ -113,10 +125,9 @@ describe('ensureTeamOrganizationForAcceptance', () => {
113125 } )
114126 expect ( updateCalls . value ) . toContainEqual ( expect . objectContaining ( { plan : 'team_6000' } ) )
115127 // The Pro→Team price migration is durably enqueued at conversion time.
116- expect ( enqueueMock ) . toHaveBeenCalledWith (
128+ expect ( mockEnqueueSubscriptionSeatsSync ) . toHaveBeenCalledWith (
117129 executor ,
118- 'stripe.sync-subscription-seats' ,
119- expect . objectContaining ( { subscriptionId : 'sub-pro' } )
130+ expect . objectContaining ( { subscriptionId : 'sub-pro' , seats : 1 } )
120131 )
121132 expect ( mockGetOrganizationSubscription ) . toHaveBeenCalledWith (
122133 'org-1' ,
@@ -203,19 +214,14 @@ describe('ensureTeamOrganizationForAcceptance', () => {
203214 expect . objectContaining ( { plan : 'team_6000' , referenceId : 'owner-1' } )
204215 )
205216 // The plan change enqueues the price seat-sync...
206- expect ( enqueueMock ) . toHaveBeenCalledWith (
217+ expect ( mockEnqueueSubscriptionSeatsSync ) . toHaveBeenCalledWith (
207218 executor ,
208- 'stripe.sync-subscription-seats' ,
209- expect . objectContaining ( { subscriptionId : 'sub-pro' } )
219+ expect . objectContaining ( { subscriptionId : 'sub-pro' , seats : 1 } )
210220 )
211221 expect ( dbChainMockFns . transaction ) . not . toHaveBeenCalled ( )
212222 expect ( lockOrder ) . toEqual ( [ 'organization' , 'subscription' ] )
213223 // ...but with no scheduled cancellation there is no cancel-sync event.
214- expect ( enqueueMock ) . not . toHaveBeenCalledWith (
215- expect . anything ( ) ,
216- 'stripe.sync-cancel-at-period-end' ,
217- expect . anything ( )
218- )
224+ expect ( mockEnqueueCancelAtPeriodEndSync ) . not . toHaveBeenCalled ( )
219225 } )
220226
221227 it ( 'blocks personal Pro conversion when the reused organization has unresolved Enterprise' , async ( ) => {
@@ -243,7 +249,8 @@ describe('ensureTeamOrganizationForAcceptance', () => {
243249 } )
244250 ) . rejects . toThrow ( 'Enterprise issuance is unfinished' )
245251 expect ( updateCalls . value ) . toHaveLength ( 0 )
246- expect ( enqueueMock ) . not . toHaveBeenCalled ( )
252+ expect ( mockEnqueueSubscriptionSeatsSync ) . not . toHaveBeenCalled ( )
253+ expect ( mockEnqueueCancelAtPeriodEndSync ) . not . toHaveBeenCalled ( )
247254 } )
248255
249256 it ( 'provisions an org for a legacy personal-scoped Team subscription without a plan change' , async ( ) => {
@@ -271,7 +278,8 @@ describe('ensureTeamOrganizationForAcceptance', () => {
271278 expect . objectContaining ( { plan : 'team' , referenceId : 'owner-1' } )
272279 )
273280 // No plan change and no scheduled cancellation: nothing to push to Stripe.
274- expect ( enqueueMock ) . not . toHaveBeenCalled ( )
281+ expect ( mockEnqueueSubscriptionSeatsSync ) . not . toHaveBeenCalled ( )
282+ expect ( mockEnqueueCancelAtPeriodEndSync ) . not . toHaveBeenCalled ( )
275283 } )
276284
277285 it ( 'returns upgrade-required (no downgrade) when no eligible Team tier exists' , async ( ) => {
0 commit comments