@@ -25,6 +25,10 @@ vi.mock('better-auth/plugins/magic-link', () => ({
2525 magicLink : vi . fn ( ( _opts ?: any ) => ( { id : 'magic-link' } ) ) ,
2626} ) ) ;
2727
28+ vi . mock ( 'better-auth/plugins/custom-session' , ( ) => ( {
29+ customSession : vi . fn ( ( fn : any ) => ( { id : 'custom-session' , _fn : fn } ) ) ,
30+ } ) ) ;
31+
2832import { betterAuth } from 'better-auth' ;
2933
3034describe ( 'AuthManager' , ( ) => {
@@ -1179,4 +1183,96 @@ describe('AuthManager', () => {
11791183 expect ( ( m as any ) . generateSecret ( ) ) . toBe ( 'a-strong-production-secret-value' ) ;
11801184 } ) ;
11811185 } ) ;
1186+
1187+ describe ( 'customSession – derived role and roles array' , ( ) => {
1188+ // dataEngine stub: `adminLinks` controls whether the user resolves as a
1189+ // platform admin (a sys_user_permission_set row pointing at the
1190+ // admin_full_access permission set with no organization scope).
1191+ const makeDataEngine = ( opts : { platformAdmin : boolean } ) => ( {
1192+ find : vi . fn ( async ( object : string ) => {
1193+ if ( object === 'sys_user_permission_set' ) {
1194+ return opts . platformAdmin
1195+ ? [ { user_id : 'u-1' , permission_set_id : 'ps-admin' , organization_id : null } ]
1196+ : [ ] ;
1197+ }
1198+ if ( object === 'sys_permission_set' ) {
1199+ return [ { id : 'ps-admin' , name : 'admin_full_access' } ] ;
1200+ }
1201+ return [ ] ;
1202+ } ) ,
1203+ findOne : vi . fn ( ) ,
1204+ } ) ;
1205+
1206+ const getSessionCallback = async ( dataEngine : any ) => {
1207+ let capturedConfig : any ;
1208+ ( betterAuth as any ) . mockImplementation ( ( config : any ) => {
1209+ capturedConfig = config ;
1210+ return { handler : vi . fn ( ) , api : { } } ;
1211+ } ) ;
1212+
1213+ const warnSpy = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
1214+ const manager = new AuthManager ( {
1215+ secret : 'test-secret-at-least-32-chars-long' ,
1216+ baseUrl : 'http://localhost:3000' ,
1217+ dataEngine,
1218+ } ) ;
1219+ await manager . getAuthInstance ( ) ;
1220+ warnSpy . mockRestore ( ) ;
1221+
1222+ const plugin = capturedConfig . plugins . find ( ( p : any ) => p . id === 'custom-session' ) ;
1223+ expect ( plugin ) . toBeDefined ( ) ;
1224+ return plugin . _fn as ( input : { user : any ; session : any } ) => Promise < any > ;
1225+ } ;
1226+
1227+ it ( 'returns roles=[] for a regular user with no stored role' , async ( ) => {
1228+ const callback = await getSessionCallback ( makeDataEngine ( { platformAdmin : false } ) ) ;
1229+ const result = await callback ( {
1230+ user : { id : 'u-1' , email : 'a@b.com' } ,
1231+ session : { } ,
1232+ } ) ;
1233+ expect ( result . user . role ) . toBeUndefined ( ) ;
1234+ expect ( result . user . roles ) . toEqual ( [ ] ) ;
1235+ } ) ;
1236+
1237+ it ( 'splits a stored role string into roles for a non-admin user' , async ( ) => {
1238+ const callback = await getSessionCallback ( makeDataEngine ( { platformAdmin : false } ) ) ;
1239+ const result = await callback ( {
1240+ user : { id : 'u-1' , email : 'a@b.com' , role : 'manager' } ,
1241+ session : { } ,
1242+ } ) ;
1243+ // No promotion: `role` keeps its stored value.
1244+ expect ( result . user . role ) . toBe ( 'manager' ) ;
1245+ expect ( result . user . roles ) . toEqual ( [ 'manager' ] ) ;
1246+ } ) ;
1247+
1248+ it ( 'keeps stored business roles in roles[] when promoting a platform admin' , async ( ) => {
1249+ const callback = await getSessionCallback ( makeDataEngine ( { platformAdmin : true } ) ) ;
1250+ const result = await callback ( {
1251+ user : { id : 'u-1' , email : 'a@b.com' , role : 'manager' } ,
1252+ session : { } ,
1253+ } ) ;
1254+ // Promotion replaces `role` (existing semantics) but the stored
1255+ // business role survives in the array.
1256+ expect ( result . user . role ) . toBe ( 'admin' ) ;
1257+ expect ( result . user . roles ) . toEqual ( [ 'manager' , 'admin' ] ) ;
1258+ } ) ;
1259+
1260+ it ( 'does not duplicate admin in roles[] when the stored role already includes it' , async ( ) => {
1261+ const callback = await getSessionCallback ( makeDataEngine ( { platformAdmin : true } ) ) ;
1262+ const result = await callback ( {
1263+ user : { id : 'u-1' , email : 'a@b.com' , role : 'admin,manager' } ,
1264+ session : { } ,
1265+ } ) ;
1266+ expect ( result . user . role ) . toBe ( 'admin' ) ;
1267+ expect ( result . user . roles ) . toEqual ( [ 'admin' , 'manager' ] ) ;
1268+ } ) ;
1269+
1270+ it ( 'returns the payload untouched when the user has no id' , async ( ) => {
1271+ const callback = await getSessionCallback ( makeDataEngine ( { platformAdmin : false } ) ) ;
1272+ const user = { email : 'anon@b.com' } ;
1273+ const result = await callback ( { user, session : { } } ) ;
1274+ expect ( result . user ) . toBe ( user ) ;
1275+ expect ( result . user . roles ) . toBeUndefined ( ) ;
1276+ } ) ;
1277+ } ) ;
11821278} ) ;
0 commit comments