@@ -45,19 +45,27 @@ describe('getComposioToolsForUser', () => {
4545 ] )
4646 } )
4747
48- function makeDb ( storedSessionId : string | null ) {
49- const findFirst = mock ( async ( ) =>
50- storedSessionId
48+ function makeDb ( storedSessionIds : string | null | Array < string | null > ) {
49+ const storedSessionIdSequence = Array . isArray ( storedSessionIds )
50+ ? [ ...storedSessionIds ]
51+ : [ storedSessionIds ]
52+ const findFirst = mock ( async ( ) => {
53+ const storedSessionId =
54+ storedSessionIdSequence . length > 1
55+ ? storedSessionIdSequence . shift ( )
56+ : storedSessionIdSequence [ 0 ]
57+
58+ return storedSessionId
5159 ? {
5260 user_id : 'user-123' ,
5361 session_id : storedSessionId ,
5462 created_at : new Date ( ) ,
5563 updated_at : new Date ( ) ,
5664 }
57- : null ,
58- )
59- const onConflictDoUpdate = mock ( async ( ) => undefined )
60- const values = mock ( ( ) => ( { onConflictDoUpdate } ) )
65+ : null
66+ } )
67+ const onConflictDoNothing = mock ( async ( ) => undefined )
68+ const values = mock ( ( ) => ( { onConflictDoNothing } ) )
6169 const whereDelete = mock ( async ( ) => undefined )
6270
6371 return {
@@ -71,7 +79,7 @@ describe('getComposioToolsForUser', () => {
7179 delete : mock ( ( ) => ( { where : whereDelete } ) ) ,
7280 } as any ,
7381 findFirst,
74- onConflictDoUpdate ,
82+ onConflictDoNothing ,
7583 values,
7684 whereDelete,
7785 }
@@ -84,7 +92,10 @@ describe('getComposioToolsForUser', () => {
8492 useSession = mock ( async ( ) => {
8593 throw notFound
8694 } )
87- const { db, whereDelete, values } = makeDb ( 'stored-session' )
95+ const { db, whereDelete, values } = makeDb ( [
96+ 'stored-session' ,
97+ 'fresh-session' ,
98+ ] )
8899
89100 const result = await getComposioToolsForUser ( {
90101 db,
@@ -112,6 +123,32 @@ describe('getComposioToolsForUser', () => {
112123 } )
113124 } )
114125
126+ test ( 'returns the persisted session when concurrent creation stores a different session' , async ( ) => {
127+ createSession = mock ( async ( ) => ( { sessionId : 'losing-session' } ) )
128+ useSession = mock ( async ( ) => ( { sessionId : 'winning-session' } ) )
129+ const { db, values, onConflictDoNothing } = makeDb ( [
130+ null ,
131+ 'winning-session' ,
132+ ] )
133+
134+ const result = await getComposioToolsForUser ( {
135+ db,
136+ userId : 'user-123' ,
137+ logger,
138+ apiKey : 'test-composio-api-key' ,
139+ } )
140+
141+ expect ( result ?. sessionId ) . toBe ( 'winning-session' )
142+ expect ( createSession ) . toHaveBeenCalledWith ( 'user-123' )
143+ expect ( values ) . toHaveBeenCalledWith ( {
144+ user_id : 'user-123' ,
145+ session_id : 'losing-session' ,
146+ } )
147+ expect ( onConflictDoNothing ) . toHaveBeenCalledTimes ( 1 )
148+ expect ( useSession ) . toHaveBeenCalledWith ( 'winning-session' )
149+ expect ( getRawToolRouterSessionTools ) . toHaveBeenCalledWith ( 'winning-session' )
150+ } )
151+
115152 test ( 'keeps the stored session row when rehydration fails transiently' , async ( ) => {
116153 const transientError = Object . assign ( new Error ( 'Composio unavailable' ) , {
117154 status : 502 ,
0 commit comments