@@ -448,3 +448,70 @@ describe('#7725 — the delegated-admin issuance scope is UNCHANGED (ADR-0090 D1
448448 expect ( invitationsFor ( engine , 'scoped.denied@example.com' ) ) . toHaveLength ( 0 ) ;
449449 } ) ;
450450} ) ;
451+
452+ describe ( '#24750 — invite-member refuses an unknown role with the ADR-0112 code ROLE_NOT_FOUND' , ( ) => {
453+ // The spec's invitation `role` docstring (`identity/organization.zod.ts`)
454+ // promises "refused at the door (`ROLE_NOT_FOUND`) before any invitation row
455+ // exists". better-auth's invite route throws that refusal with a message and
456+ // no `code`; `update-member-role` throws the same refusal WITH one. These pin
457+ // the door's answer, the untouched valid-role path, and the control door.
458+ beforeEach ( ( ) => {
459+ vi . spyOn ( console , 'info' ) . mockImplementation ( ( ) => { } ) ;
460+ vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
461+ } ) ;
462+
463+ afterEach ( ( ) => {
464+ vi . restoreAllMocks ( ) ;
465+ } ) ;
466+
467+ it ( 'an unknown role is refused 400 with code ROLE_NOT_FOUND, the message names the role, and no invitation row exists' , async ( ) => {
468+ const { engine, manager, owner } = await bootWithOwner ( ) ;
469+
470+ const res = await post (
471+ manager ,
472+ '/organization/invite-member' ,
473+ { email : 'qa-invitee@example.com' , role : 'no_such_role' , organizationId : DEFAULT_ORG } ,
474+ owner . cookie ,
475+ ) ;
476+ expect ( res . status ) . toBe ( 400 ) ;
477+ const body : any = await res . json ( ) ;
478+ expect ( body . code ) . toBe ( 'ROLE_NOT_FOUND' ) ;
479+ expect ( body . message ) . toContain ( 'no_such_role' ) ;
480+ expect ( invitationsFor ( engine , 'qa-invitee@example.com' ) ) . toHaveLength ( 0 ) ;
481+ } ) ;
482+
483+ it ( 'CONTROL: a valid role is unchanged — 200 and one pending row carrying that role' , async ( ) => {
484+ const { engine, manager, owner } = await bootWithOwner ( ) ;
485+
486+ const res = await post (
487+ manager ,
488+ '/organization/invite-member' ,
489+ { email : 'qa-valid@example.com' , role : 'member' , organizationId : DEFAULT_ORG } ,
490+ owner . cookie ,
491+ ) ;
492+ expect ( res . status , await res . clone ( ) . text ( ) ) . toBe ( 200 ) ;
493+ const rows = invitationsFor ( engine , 'qa-valid@example.com' ) ;
494+ expect ( rows ) . toHaveLength ( 1 ) ;
495+ expect ( rows [ 0 ] ! . status ) . toBe ( 'pending' ) ;
496+ expect ( rows [ 0 ] ! . role ) . toBe ( 'member' ) ;
497+ } ) ;
498+
499+ it ( 'CONTROL: update-member-role keeps the code it already answered with — 400 ROLE_NOT_FOUND, role unchanged' , async ( ) => {
500+ const { engine, manager, owner } = await bootWithOwner ( ) ;
501+ const colleague = await signUp ( manager , engine , 'colleague@example.com' ) ;
502+ const [ membership ] = membersOf ( engine , DEFAULT_ORG , colleague . userId ) ;
503+ expect ( membership , 'the reconciler did not bind the colleague' ) . toBeDefined ( ) ;
504+
505+ const res = await post (
506+ manager ,
507+ '/organization/update-member-role' ,
508+ { memberId : String ( membership ! . id ) , role : 'no_such_role' , organizationId : DEFAULT_ORG } ,
509+ owner . cookie ,
510+ ) ;
511+ expect ( res . status ) . toBe ( 400 ) ;
512+ const body : any = await res . json ( ) ;
513+ expect ( body . code ) . toBe ( 'ROLE_NOT_FOUND' ) ;
514+ expect ( body . message ) . toContain ( 'no_such_role' ) ;
515+ expect ( membersOf ( engine , DEFAULT_ORG , colleague . userId ) [ 0 ] ! . role ) . toBe ( 'member' ) ;
516+ } ) ;
517+ } ) ;
0 commit comments