11// External mock objects created before jest.mock()
2+ const mockSession = {
3+ withTransaction : jest . fn ( ) ,
4+ endSession : jest . fn ( ) ,
5+ } ;
6+
7+ const mockClient = {
8+ startSession : jest . fn ( ( ) => mockSession ) ,
9+ } ;
10+
211const mockUsersCollection = {
312 findOne : jest . fn ( ) ,
413 updateOne : jest . fn ( ) ,
@@ -14,6 +23,7 @@ const mockTeamsCollection = {
1423
1524const mockDbInstance = {
1625 connect : jest . fn ( ) ,
26+ getClient : jest . fn ( ( ) => mockClient ) ,
1727 getCollection : jest . fn ( ( name : string ) => {
1828 if ( name === 'users' ) return mockUsersCollection ;
1929 if ( name === 'teams' ) return mockTeamsCollection ;
@@ -49,6 +59,12 @@ describe('Teams Create Handler', () => {
4959 mockTeamsCollection . insertOne . mockResolvedValue ( { acknowledged : true } ) ;
5060 mockUsersCollection . updateOne . mockResolvedValue ( { acknowledged : true } ) ;
5161 mockTeamsCollection . deleteOne . mockResolvedValue ( { acknowledged : true } ) ;
62+
63+ // Mock transaction to execute the callback immediately
64+ mockSession . withTransaction . mockImplementation ( async ( callback ) => {
65+ return await callback ( ) ;
66+ } ) ;
67+ mockSession . endSession . mockResolvedValue ( undefined ) ;
5268 } ) ;
5369
5470 const mockEventData = {
@@ -92,7 +108,8 @@ describe('Teams Create Handler', () => {
92108 team_name : 'Test Team' ,
93109 members : [ ] ,
94110 status : 'Active' ,
95- } )
111+ } ) ,
112+ expect . objectContaining ( { session : mockSession } )
96113 ) ;
97114
98115 // Verify leader update
@@ -106,7 +123,8 @@ describe('Teams Create Handler', () => {
106123 pending_invites : [ ] ,
107124 } ) ,
108125 } ,
109- }
126+ } ,
127+ expect . objectContaining ( { session : mockSession } )
110128 ) ;
111129
112130 // Verify invitations were sent
@@ -219,7 +237,7 @@ describe('Teams Create Handler', () => {
219237 expect ( body . message ) . toBe ( 'Team size cannot exceed 4 members (including leader)' ) ;
220238 } ) ;
221239
222- it ( 'should clean up team when invitation sending fails' , async ( ) => {
240+ it ( 'should rollback transaction when invitation sending fails' , async ( ) => {
223241 // Setup successful validation
224242 mockUsersCollection . findOne
225243 . mockResolvedValueOnce ( mockLeaderUser ) // Auth user lookup
@@ -237,18 +255,15 @@ describe('Teams Create Handler', () => {
237255
238256 expect ( result . statusCode ) . toBe ( 500 ) ;
239257 const body = JSON . parse ( result . body ) ;
240- expect ( body . message ) . toBe ( 'Team created but failed to send invitations ' ) ;
258+ expect ( body . message ) . toBe ( 'Failed to create team ' ) ;
241259
242- // Verify cleanup: team should be deleted
243- expect ( mockTeamsCollection . deleteOne ) . toHaveBeenCalledWith (
244- expect . objectContaining ( { team_id : expect . any ( String ) } )
245- ) ;
260+ // Verify transaction was attempted
261+ expect ( mockSession . withTransaction ) . toHaveBeenCalled ( ) ;
262+
263+ // Verify session cleanup
264+ expect ( mockSession . endSession ) . toHaveBeenCalled ( ) ;
246265
247- // Verify cleanup: leader's team info should be removed
248- expect ( mockUsersCollection . updateOne ) . toHaveBeenCalledWith (
249- { email : 'leader@test.com' } ,
250- { $unset : { team_info : '' , confirmed_team : '' } }
251- ) ;
266+ // No manual cleanup needed since transaction handles rollback automatically
252267 } ) ;
253268
254269 it ( 'should handle database errors gracefully' , async ( ) => {
@@ -284,7 +299,8 @@ describe('Teams Create Handler', () => {
284299 expect ( mockTeamsCollection . insertOne ) . toHaveBeenCalledWith (
285300 expect . objectContaining ( {
286301 leader_email : 'leader@test.com' , // Should be lowercase
287- } )
302+ } ) ,
303+ expect . objectContaining ( { session : mockSession } )
288304 ) ;
289305
290306 // Verify invitations are sent with lowercase emails
@@ -295,4 +311,74 @@ describe('Teams Create Handler', () => {
295311 [ 'member1@test.com' , 'member2@test.com' ] // Should be lowercase
296312 ) ;
297313 } ) ;
314+
315+ it ( 'should return 400 for empty team name' , async ( ) => {
316+ const emptyNameData = {
317+ ...mockEventData ,
318+ team_name : ' ' , // Just whitespace
319+ } ;
320+
321+ const mockEvent = createEvent ( emptyNameData , '/teams/create' , 'POST' ) ;
322+ const result = await main ( mockEvent , mockContext ) ;
323+
324+ expect ( result . statusCode ) . toBe ( 400 ) ;
325+ const body = JSON . parse ( result . body ) ;
326+ expect ( body . message ) . toBe ( 'Team name cannot be empty' ) ;
327+ } ) ;
328+
329+ it ( 'should return 400 for team name exceeding 50 characters' , async ( ) => {
330+ const longNameData = {
331+ ...mockEventData ,
332+ team_name : 'A' . repeat ( 51 ) , // 51 characters
333+ } ;
334+
335+ const mockEvent = createEvent ( longNameData , '/teams/create' , 'POST' ) ;
336+ const result = await main ( mockEvent , mockContext ) ;
337+
338+ expect ( result . statusCode ) . toBe ( 400 ) ;
339+ const body = JSON . parse ( result . body ) ;
340+ expect ( body . message ) . toBe ( 'Team name cannot exceed 50 characters' ) ;
341+ } ) ;
342+
343+ it ( 'should return 400 for team name with invalid characters' , async ( ) => {
344+ const invalidNameData = {
345+ ...mockEventData ,
346+ team_name : 'Team@Name!' , // Contains @ and !
347+ } ;
348+
349+ const mockEvent = createEvent ( invalidNameData , '/teams/create' , 'POST' ) ;
350+ const result = await main ( mockEvent , mockContext ) ;
351+
352+ expect ( result . statusCode ) . toBe ( 400 ) ;
353+ const body = JSON . parse ( result . body ) ;
354+ expect ( body . message ) . toBe ( 'Team name can only contain letters, numbers, spaces, hyphens, and underscores' ) ;
355+ } ) ;
356+
357+ it ( 'should accept valid team name with allowed characters' , async ( ) => {
358+ const validNameData = {
359+ ...mockEventData ,
360+ team_name : 'Team_Name-123 Valid' , // Valid characters
361+ } ;
362+
363+ // Mock user lookups for validation
364+ mockUsersCollection . findOne
365+ . mockResolvedValueOnce ( mockLeaderUser ) // Auth user lookup
366+ . mockResolvedValueOnce ( mockMemberUser ) // member1@test .com lookup
367+ . mockResolvedValueOnce ( mockMemberUser ) ; // member2@test .com lookup
368+
369+ const mockEvent = createEvent ( validNameData , '/teams/create' , 'POST' ) ;
370+ const result = await main ( mockEvent , mockContext ) ;
371+
372+ expect ( result . statusCode ) . toBe ( 200 ) ;
373+ const body = JSON . parse ( result . body ) ;
374+ expect ( body . message ) . toBe ( 'Team created successfully' ) ;
375+
376+ // Verify team creation uses trimmed name
377+ expect ( mockTeamsCollection . insertOne ) . toHaveBeenCalledWith (
378+ expect . objectContaining ( {
379+ team_name : 'Team_Name-123 Valid' ,
380+ } ) ,
381+ expect . objectContaining ( { session : mockSession } )
382+ ) ;
383+ } ) ;
298384} ) ;
0 commit comments