11/**
22 * @vitest -environment node
33 */
4+ import {
5+ dbChainMockFns ,
6+ resetDbChainMock ,
7+ workflowsPersistenceUtilsMock ,
8+ workflowsPersistenceUtilsMockFns ,
9+ } from '@sim/testing'
410import { beforeEach , describe , expect , it , vi } from 'vitest'
511
612const {
7- mockTransaction,
8- mockIsWorkspaceCreationGovernedByPermissionGroups,
13+ mockResolveGoverningPermissionGroupOrganization,
914 mockLockWorkspaceCreationContext,
1015 mockGetWorkspaceInvitePolicy,
11- mockSaveWorkflowToNormalizedTables,
1216} = vi . hoisted ( ( ) => ( {
13- mockTransaction : vi . fn ( ) ,
14- mockIsWorkspaceCreationGovernedByPermissionGroups : vi . fn ( ) ,
17+ mockResolveGoverningPermissionGroupOrganization : vi . fn ( ) ,
1518 mockLockWorkspaceCreationContext : vi . fn ( ) ,
1619 mockGetWorkspaceInvitePolicy : vi . fn ( ) ,
17- mockSaveWorkflowToNormalizedTables : vi . fn ( ) ,
18- } ) )
19-
20- vi . mock ( '@sim/db' , ( ) => ( {
21- db : { transaction : mockTransaction } ,
2220} ) )
2321
2422/** The starter workflow is not what these cases are about, and it reaches the block registry. */
25- vi . mock ( '@/lib/workflows/persistence/utils' , ( ) => ( {
26- saveWorkflowToNormalizedTables : mockSaveWorkflowToNormalizedTables ,
27- } ) )
23+ vi . mock ( '@/lib/workflows/persistence/utils' , ( ) => workflowsPersistenceUtilsMock )
2824
2925vi . mock ( '@/lib/workflows/defaults' , ( ) => ( {
3026 buildDefaultWorkflowArtifacts : ( ) => ( { workflowState : { } } ) ,
@@ -34,8 +30,7 @@ vi.mock('@/lib/workspaces/policy', async (importOriginal) => {
3430 const actual = await importOriginal < typeof import ( '@/lib/workspaces/policy' ) > ( )
3531 return {
3632 ...actual ,
37- isWorkspaceCreationGovernedByPermissionGroups :
38- mockIsWorkspaceCreationGovernedByPermissionGroups ,
33+ resolveGoverningPermissionGroupOrganization : mockResolveGoverningPermissionGroupOrganization ,
3934 lockWorkspaceCreationContext : mockLockWorkspaceCreationContext ,
4035 getWorkspaceInvitePolicy : mockGetWorkspaceInvitePolicy ,
4136 }
@@ -60,29 +55,27 @@ const params = {
6055describe ( 'createWorkspace capability-gate placement' , ( ) => {
6156 beforeEach ( ( ) => {
6257 vi . clearAllMocks ( )
58+ resetDbChainMock ( )
6359 mockGetWorkspaceInvitePolicy . mockResolvedValue ( { } )
6460 } )
6561
6662 /**
67- * The ENTITLEMENT half must be settled before the transaction opens: it
68- * bottoms out in the `cache()`d `isOrganizationOnEnterprisePlan`, which admits
69- * no executor, so running it inside checked out a SECOND pooled connection
70- * while three advisory locks were held — what `packages/db/tx-tripwire.ts`
71- * fires on, and what pushed concurrent creates past the 5s `lock_timeout`.
63+ * The ENTITLEMENT half must be settled before the transaction opens — see
64+ * {@link resolveGoverningPermissionGroupOrganization}.
7265 *
7366 * Asserted as an explicit ordering rather than inferred from the absence of a
7467 * tripwire warning: nothing else in the unit suite can catch a regression
7568 * here, because `vitest.setup.ts` mocks `@sim/db` globally and the real pool
7669 * instrumentation never runs.
7770 */
7871 it ( 'resolves the permission regime before opening the transaction' , async ( ) => {
79- mockIsWorkspaceCreationGovernedByPermissionGroups . mockResolvedValue ( true )
72+ mockResolveGoverningPermissionGroupOrganization . mockResolvedValue ( 'org-1' )
8073 /**
8174 * The callback is deliberately NOT invoked, so the transaction's own
8275 * internals stay out of the assertion and cannot fail it for an unrelated
8376 * reason.
8477 */
85- mockTransaction . mockResolvedValue ( {
78+ dbChainMockFns . transaction . mockResolvedValue ( {
8679 id : 'ws-1' ,
8780 name : params . name ,
8881 organizationId : 'org-1' ,
@@ -93,27 +86,27 @@ describe('createWorkspace capability-gate placement', () => {
9386
9487 await createWorkspace ( params )
9588
96- expect ( mockIsWorkspaceCreationGovernedByPermissionGroups ) . toHaveBeenCalledWith ( {
89+ expect ( mockResolveGoverningPermissionGroupOrganization ) . toHaveBeenCalledWith ( {
9790 organizationId : 'org-1' ,
9891 observedOrganizationId : 'org-1' ,
9992 } )
10093 expect (
101- mockIsWorkspaceCreationGovernedByPermissionGroups . mock . invocationCallOrder [ 0 ]
102- ) . toBeLessThan ( mockTransaction . mock . invocationCallOrder [ 0 ] )
94+ mockResolveGoverningPermissionGroupOrganization . mock . invocationCallOrder [ 0 ]
95+ ) . toBeLessThan ( dbChainMockFns . transaction . mock . invocationCallOrder [ 0 ] )
10396 } )
10497
10598 /**
10699 * The capability itself is enforced INSIDE the transaction, under the
107- * permission-group lock — so the regime answer has to reach
100+ * permission-group lock — so the governing organization has to reach
108101 * `lockWorkspaceCreationContext`. Dropping it there would silently skip the
109102 * gate for every governed organization.
110103 */
111- it ( 'carries the regime answer into the locked creation context' , async ( ) => {
112- mockIsWorkspaceCreationGovernedByPermissionGroups . mockResolvedValue ( true )
104+ it ( 'carries the governing organization into the locked creation context' , async ( ) => {
105+ mockResolveGoverningPermissionGroupOrganization . mockResolvedValue ( 'org-1' )
113106 mockLockWorkspaceCreationContext . mockResolvedValue ( { billedAccountUserId : 'creator-1' } )
114107 const tx = { insert : vi . fn ( ( ) => ( { values : vi . fn ( ) } ) ) } as unknown as DbOrTx
115- mockTransaction . mockImplementation ( ( callback : ( executor : DbOrTx ) => Promise < unknown > ) =>
116- callback ( tx )
108+ dbChainMockFns . transaction . mockImplementation (
109+ ( callback : ( executor : DbOrTx ) => Promise < unknown > ) => callback ( tx )
117110 )
118111
119112 await createWorkspace ( { ...params , skipDefaultWorkflow : true } )
@@ -122,14 +115,40 @@ describe('createWorkspace capability-gate placement', () => {
122115 userId : 'creator-1' ,
123116 organizationId : 'org-1' ,
124117 observedOrganizationId : 'org-1' ,
125- permissionGroupsGovernCreation : true ,
118+ governingPermissionGroupOrganizationId : 'org-1' ,
119+ } )
120+ } )
121+
122+ /**
123+ * The preflight policy resolved this value microseconds earlier in the same
124+ * request, and React's `cache()` memo does not span the two calls, so a
125+ * forwarded answer must be used as-is rather than re-read.
126+ */
127+ it ( 'reuses the governing organization the caller already resolved' , async ( ) => {
128+ mockLockWorkspaceCreationContext . mockResolvedValue ( { billedAccountUserId : 'creator-1' } )
129+ const tx = { insert : vi . fn ( ( ) => ( { values : vi . fn ( ) } ) ) } as unknown as DbOrTx
130+ dbChainMockFns . transaction . mockImplementation (
131+ ( callback : ( executor : DbOrTx ) => Promise < unknown > ) => callback ( tx )
132+ )
133+
134+ await createWorkspace ( {
135+ ...params ,
136+ skipDefaultWorkflow : true ,
137+ governingPermissionGroupOrganizationId : 'org-1' ,
126138 } )
139+
140+ expect ( mockResolveGoverningPermissionGroupOrganization ) . not . toHaveBeenCalled ( )
141+ expect ( mockLockWorkspaceCreationContext ) . toHaveBeenCalledWith (
142+ tx ,
143+ expect . objectContaining ( { governingPermissionGroupOrganizationId : 'org-1' } )
144+ )
127145 } )
128146} )
129147
130148describe ( 'createDefaultPersonalWorkspaceInTransaction' , ( ) => {
131149 beforeEach ( ( ) => {
132150 vi . clearAllMocks ( )
151+ resetDbChainMock ( )
133152 } )
134153
135154 /**
@@ -147,12 +166,27 @@ describe('createDefaultPersonalWorkspaceInTransaction', () => {
147166 userName : 'Ada Lovelace' ,
148167 } )
149168
150- expect ( mockIsWorkspaceCreationGovernedByPermissionGroups ) . not . toHaveBeenCalled ( )
169+ expect ( mockResolveGoverningPermissionGroupOrganization ) . not . toHaveBeenCalled ( )
151170 expect ( mockLockWorkspaceCreationContext ) . toHaveBeenCalledWith ( tx , {
152171 userId : 'user-1' ,
153172 organizationId : null ,
154173 observedOrganizationId : null ,
155- permissionGroupsGovernCreation : false ,
174+ governingPermissionGroupOrganizationId : null ,
156175 } )
157176 } )
177+
178+ /** The starter workflow is built before the locks, so it must still be written. */
179+ it ( 'seeds the starter workflow it built before taking the locks' , async ( ) => {
180+ mockLockWorkspaceCreationContext . mockResolvedValue ( { billedAccountUserId : 'user-1' } )
181+ const tx = { insert : vi . fn ( ( ) => ( { values : vi . fn ( ) } ) ) } as unknown as DbOrTx
182+
183+ await createDefaultPersonalWorkspaceInTransaction ( tx , {
184+ userId : 'user-1' ,
185+ userName : 'Ada Lovelace' ,
186+ } )
187+
188+ expect (
189+ workflowsPersistenceUtilsMockFns . mockSaveWorkflowToNormalizedTables
190+ ) . toHaveBeenCalledWith ( expect . any ( String ) , { } , { workspaceId : null , subjectUserId : null } , tx )
191+ } )
158192} )
0 commit comments