@@ -21,12 +21,14 @@ describe('UsersService', () => {
2121 findOne : jest . fn ( ) ,
2222 create : jest . fn ( ) ,
2323 save : jest . fn ( ) ,
24+ increment : jest . fn ( ) ,
2425 findAndCount : jest . fn ( ) ,
2526 } ;
2627
2728 const mockRoleRepo = {
2829 create : jest . fn ( ) ,
2930 save : jest . fn ( ) ,
31+ delete : jest . fn ( ) ,
3032 } ;
3133
3234 const mockMentorProfileRepo = {
@@ -387,6 +389,98 @@ describe('UsersService', () => {
387389 } ) ;
388390 } ) ;
389391
392+ describe ( 'createUser' , ( ) => {
393+ it ( 'should create a user with a default mentee role' , async ( ) => {
394+ const createdUser = { ...mockUser , id : 'user-2' } ;
395+ mockUserRepo . create . mockReturnValue ( createdUser ) ;
396+ mockUserRepo . save . mockResolvedValue ( createdUser ) ;
397+ mockRoleRepo . create . mockReturnValue ( {
398+ name : AuthRole . MENTEE ,
399+ user : createdUser ,
400+ } ) ;
401+ mockRoleRepo . save . mockResolvedValue ( { } ) ;
402+ mockUserRepo . findOne . mockResolvedValue ( {
403+ ...createdUser ,
404+ roles : [ { name : AuthRole . MENTEE } ] ,
405+ } ) ;
406+
407+ const result = await service . createUser ( 'new-wallet' ) ;
408+
409+ expect ( mockUserRepo . create ) . toHaveBeenCalledWith ( {
410+ walletAddress : 'new-wallet' ,
411+ } ) ;
412+ expect ( mockRoleRepo . create ) . toHaveBeenCalledWith ( {
413+ name : AuthRole . MENTEE ,
414+ user : createdUser ,
415+ } ) ;
416+ expect ( result . roles ) . toEqual ( [ { name : AuthRole . MENTEE } ] ) ;
417+ } ) ;
418+ } ) ;
419+
420+ describe ( 'assignRole' , ( ) => {
421+ it ( 'should assign a new role and bump the token version' , async ( ) => {
422+ mockUserRepo . findOne . mockResolvedValue ( { ...mockUser , roles : [ ] } ) ;
423+ mockRoleRepo . create . mockReturnValue ( {
424+ name : AuthRole . ADMIN ,
425+ user : mockUser ,
426+ } ) ;
427+ mockRoleRepo . save . mockResolvedValue ( { } ) ;
428+ mockUserRepo . increment . mockResolvedValue ( { } ) ;
429+
430+ await service . assignRole ( 'user-1' , AuthRole . ADMIN ) ;
431+
432+ expect ( mockRoleRepo . create ) . toHaveBeenCalledWith ( {
433+ name : AuthRole . ADMIN ,
434+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
435+ user : expect . objectContaining ( { id : 'user-1' } ) ,
436+ } ) ;
437+ expect ( mockUserRepo . increment ) . toHaveBeenCalledWith (
438+ { id : 'user-1' } ,
439+ 'tokenVersion' ,
440+ 1 ,
441+ ) ;
442+ } ) ;
443+
444+ it ( 'should throw ConflictException when user already has the role' , async ( ) => {
445+ mockUserRepo . findOne . mockResolvedValue ( {
446+ ...mockUser ,
447+ roles : [ { name : AuthRole . ADMIN } ] ,
448+ } ) ;
449+
450+ await expect (
451+ service . assignRole ( 'user-1' , AuthRole . ADMIN ) ,
452+ ) . rejects . toThrow ( ConflictException ) ;
453+ } ) ;
454+ } ) ;
455+
456+ describe ( 'revokeRole' , ( ) => {
457+ it ( 'should revoke an existing role and bump the token version' , async ( ) => {
458+ mockUserRepo . findOne . mockResolvedValue ( {
459+ ...mockUser ,
460+ roles : [ { id : 'role-1' , name : AuthRole . ADMIN } ] ,
461+ } ) ;
462+ mockRoleRepo . delete . mockResolvedValue ( { } ) ;
463+ mockUserRepo . increment . mockResolvedValue ( { } ) ;
464+
465+ await service . revokeRole ( 'user-1' , AuthRole . ADMIN ) ;
466+
467+ expect ( mockRoleRepo . delete ) . toHaveBeenCalledWith ( { id : 'role-1' } ) ;
468+ expect ( mockUserRepo . increment ) . toHaveBeenCalledWith (
469+ { id : 'user-1' } ,
470+ 'tokenVersion' ,
471+ 1 ,
472+ ) ;
473+ } ) ;
474+
475+ it ( 'should throw NotFoundException when user does not have the role' , async ( ) => {
476+ mockUserRepo . findOne . mockResolvedValue ( { ...mockUser , roles : [ ] } ) ;
477+
478+ await expect (
479+ service . revokeRole ( 'user-1' , AuthRole . ADMIN ) ,
480+ ) . rejects . toThrow ( NotFoundException ) ;
481+ } ) ;
482+ } ) ;
483+
390484 describe ( 'updateUser' , ( ) => {
391485 it ( 'should update user fields and save' , async ( ) => {
392486 const existingUser = { ...mockUser , email : undefined } ;
@@ -428,6 +522,25 @@ describe('UsersService', () => {
428522 } ) ;
429523 } ) ;
430524
525+ describe ( 'getTokenVersion' , ( ) => {
526+ it ( 'should return the token version for an existing user' , async ( ) => {
527+ mockUserRepo . findOne . mockResolvedValue ( {
528+ id : 'user-1' ,
529+ tokenVersion : 3 ,
530+ } ) ;
531+
532+ const result = await service . getTokenVersion ( 'user-1' ) ;
533+ expect ( result ) . toBe ( 3 ) ;
534+ } ) ;
535+
536+ it ( 'should return null when the user does not exist' , async ( ) => {
537+ mockUserRepo . findOne . mockResolvedValue ( null ) ;
538+
539+ const result = await service . getTokenVersion ( 'missing' ) ;
540+ expect ( result ) . toBeNull ( ) ;
541+ } ) ;
542+ } ) ;
543+
431544 describe ( 'deactivateUser' , ( ) => {
432545 it ( 'should set status to deleted and save' , async ( ) => {
433546 const existingUser = { ...mockUser , status : UserStatus . ACTIVE } ;
0 commit comments