@@ -216,11 +216,19 @@ describe('Client auth gate (Phase 3) — session init integration', () => {
216216 } ;
217217 transport . setProfileContextProvider ( async ( ) => profileContext ) ;
218218
219+ const logger = ( transport as unknown as { logger : { warn : ReturnType < typeof vi . fn > } } ) . logger ;
220+ const warnSpy = vi . spyOn ( logger , 'warn' ) ;
221+
219222 const req = makeReq ( ) ;
220223 const res = makeRes ( ) ;
221224 await ( transport as unknown as { handlePost : ( r : unknown , s : unknown ) => Promise < void > } ) . handlePost ( req , res ) ;
222225
223226 expect ( res . statusCode ) . toBe ( 401 ) ;
227+ expect ( res . body ) . toEqual ( expect . objectContaining ( { error : 'Unauthorized' , message : 'Client authentication failed' } ) ) ;
228+ expect ( warnSpy ) . toHaveBeenCalledWith (
229+ 'Client auth gate rejected session init' ,
230+ expect . objectContaining ( { errorType : 'ClientAuthGateError' , profileId : 'default' } ) ,
231+ ) ;
224232 } ) ;
225233
226234 // Scenario 5
@@ -269,11 +277,19 @@ describe('Client auth gate (Phase 3) — session init integration', () => {
269277 } ;
270278 transport . setProfileContextProvider ( async ( ) => profileContext ) ;
271279
280+ const logger = ( transport as unknown as { logger : { warn : ReturnType < typeof vi . fn > } } ) . logger ;
281+ const warnSpy = vi . spyOn ( logger , 'warn' ) ;
282+
272283 const req = makeReq ( ) ;
273284 const res = makeRes ( ) ;
274285 await ( transport as unknown as { handlePost : ( r : unknown , s : unknown ) => Promise < void > } ) . handlePost ( req , res ) ;
275286
276287 expect ( res . statusCode ) . toBe ( 401 ) ;
288+ expect ( res . body ) . toEqual ( expect . objectContaining ( { error : 'Unauthorized' , message : 'Client authentication failed' } ) ) ;
289+ expect ( warnSpy ) . toHaveBeenCalledWith (
290+ 'Client auth gate rejected session init' ,
291+ expect . objectContaining ( { errorType : 'ClientAuthGateError' , profileId : 'default' } ) ,
292+ ) ;
277293 } ) ;
278294
279295 // Scenario 7
@@ -339,10 +355,14 @@ describe('Client auth gate (Phase 3) — session init integration', () => {
339355 await ( transport as unknown as { handlePost : ( r : unknown , s : unknown ) => Promise < void > } ) . handlePost ( req , res ) ;
340356
341357 expect ( res . statusCode ) . toBe ( 401 ) ;
342- expect ( res . body ) . toEqual ( expect . objectContaining ( { error : 'Unauthorized' } ) ) ;
358+ expect ( res . body ) . toEqual ( expect . objectContaining ( { error : 'Unauthorized' , message : 'Client authentication failed' } ) ) ;
343359 expect ( warnSpy ) . toHaveBeenCalledWith (
344360 'Client auth gate rejected session init' ,
345- expect . objectContaining ( { errorType : 'unknown' } ) ,
361+ expect . objectContaining ( {
362+ errorType : 'unknown' ,
363+ profileId : 'default' ,
364+ error : 'upstream store unreachable' ,
365+ } ) ,
346366 ) ;
347367 } ) ;
348368
@@ -370,10 +390,81 @@ describe('Client auth gate (Phase 3) — session init integration', () => {
370390 await ( transport as unknown as { handlePost : ( r : unknown , s : unknown ) => Promise < void > } ) . handlePost ( req , res ) ;
371391
372392 expect ( res . statusCode ) . toBe ( 401 ) ;
393+ expect ( res . body ) . toEqual ( expect . objectContaining ( { error : 'Unauthorized' , message : 'Client authentication failed' } ) ) ;
373394 expect ( warnSpy ) . toHaveBeenCalledWith (
374395 'Client auth gate rejected session init' ,
375- expect . objectContaining ( { errorType : 'ClientAuthGateError' } ) ,
396+ expect . objectContaining ( {
397+ errorType : 'ClientAuthGateError' ,
398+ profileId : 'default' ,
399+ } ) ,
400+ ) ;
401+ } ) ;
402+
403+ // Scenario 11 — mode=optional + valid key → principal populated
404+ it ( 'mode=optional + valid key -> 200 + session.clientPrincipal populated' , async ( ) => {
405+ // Pins that optional mode does NOT degrade all requests to anonymous — a client
406+ // that presents a valid key still receives a populated principal for audit/policy.
407+ const profileContext : HttpProfileContext = {
408+ profileId : 'default' ,
409+ client_auth_gate : {
410+ mode : 'optional' ,
411+ api_keys : {
412+ type : 'inline' ,
413+ keys : [ { key_from_env : VALID_KEY_ENV , subject : SUBJECT , scopes : [ 'read' ] } ] ,
414+ } ,
415+ } satisfies ClientAuthGateConfig ,
416+ } ;
417+ transport . setProfileContextProvider ( async ( ) => profileContext ) ;
418+
419+ const req = makeReq ( `Bearer ${ VALID_KEY } ` ) ;
420+ const res = makeRes ( ) ;
421+ await ( transport as unknown as { handlePost : ( r : unknown , s : unknown ) => Promise < void > } ) . handlePost ( req , res ) ;
422+
423+ expect ( res . statusCode ) . toBe ( 200 ) ;
424+ const sessionId = res . headers [ 'Mcp-Session-Id' ] ;
425+ expect ( sessionId ) . toBeTruthy ( ) ;
426+ const session = getSession ( transport , 'default' , sessionId ! ) as
427+ | { clientPrincipal ?: { authType : string ; subject : string ; scopes : string [ ] } }
428+ | undefined ;
429+ expect ( session ) . toBeDefined ( ) ;
430+ expect ( session ! . clientPrincipal ) . toBeDefined ( ) ;
431+ expect ( session ! . clientPrincipal ! . authType ) . toBe ( 'token' ) ;
432+ expect ( session ! . clientPrincipal ! . subject ) . toBe ( SUBJECT ) ;
433+ expect ( session ! . clientPrincipal ! . scopes ) . toEqual ( [ 'read' ] ) ;
434+ } ) ;
435+
436+ // Scenario 12 — gate-initialized log fires with correct fields
437+ it ( 'gate construction logs "Client auth gate initialized" with mode and hasApiKeys' , async ( ) => {
438+ // Pins that the initialization log (used by ops to confirm gate config at startup)
439+ // includes the correct structured fields.
440+ const profileContext : HttpProfileContext = {
441+ profileId : 'default' ,
442+ client_auth_gate : {
443+ mode : 'required' ,
444+ api_keys : {
445+ type : 'inline' ,
446+ keys : [ { key_from_env : VALID_KEY_ENV , subject : SUBJECT } ] ,
447+ } ,
448+ } ,
449+ } ;
450+ transport . setProfileContextProvider ( async ( ) => profileContext ) ;
451+
452+ const logger = ( transport as unknown as { logger : { info : ReturnType < typeof vi . fn > } } ) . logger ;
453+ const infoSpy = vi . spyOn ( logger , 'info' ) ;
454+
455+ // Drive a request to trigger getProfileState() and gate construction.
456+ const req = makeReq ( `Bearer ${ VALID_KEY } ` ) ;
457+ const res = makeRes ( ) ;
458+ await ( transport as unknown as { handlePost : ( r : unknown , s : unknown ) => Promise < void > } ) . handlePost ( req , res ) ;
459+
460+ const initCall = infoSpy . mock . calls . find (
461+ ( c ) => typeof c [ 0 ] === 'string' && c [ 0 ] . includes ( 'Client auth gate initialized' ) ,
376462 ) ;
463+ expect ( initCall ) . toBeDefined ( ) ;
464+ const logFields = initCall ! [ 1 ] as Record < string , unknown > ;
465+ expect ( logFields [ 'mode' ] ) . toBe ( 'required' ) ;
466+ expect ( logFields [ 'hasApiKeys' ] ) . toBe ( true ) ;
467+ expect ( logFields [ 'profileId' ] ) . toBe ( 'default' ) ;
377468 } ) ;
378469
379470 // Scenario 10 — session-creation log includes clientSubject + clientAuthType
0 commit comments