@@ -101,48 +101,34 @@ describe("CalUnifiedCalendarsController", () => {
101101 expect ( result . data . connections ) . toEqual ( [ ] ) ;
102102 } ) ;
103103
104- it ( "should not include credential key even if service accidentally includes it" , async ( ) => {
105- // Simulate a regression where the service layer accidentally includes credential key.
106- // The controller passes through service data directly, so if the service leaks key,
107- // it will appear in the response. This test documents that the protection lives in
108- // UnifiedCalendarsFreebusyService.getConnections() which only returns
109- // { connectionId, type, email } — never credential.key.
104+ it ( "should strip credential key even if service accidentally includes it (defense-in-depth)" , async ( ) => {
105+ // The controller destructures only { connectionId, type, email } from each connection,
106+ // so even if the service layer has a regression that leaks credential.key, the
107+ // controller response will never contain it.
110108 const connectionsWithKey = [
111109 {
112110 connectionId : "1" ,
113111 type : "google" as const ,
114112 email : "user@gmail.com" ,
115- key : { access_token : "SECRET " } ,
113+ key : { access_token : "SECRET_TOKEN" , refresh_token : "SECRET_REFRESH " } ,
116114 } ,
117115 ] ;
118116 mockFreebusyService . getConnections . mockResolvedValue ( connectionsWithKey ) ;
119117
120- const result = await controller . listConnections ( userId ) ;
121- const serialized = JSON . stringify ( result ) ;
122-
123- // Verify core fields are present
124- expect ( result . data . connections [ 0 ] ) . toHaveProperty ( "connectionId" , "1" ) ;
125- expect ( result . data . connections [ 0 ] ) . toHaveProperty ( "type" , "google" ) ;
126- expect ( result . data . connections [ 0 ] ) . toHaveProperty ( "email" , "user@gmail.com" ) ;
127- // Document: controller currently passes through extra fields — key leak prevention
128- // is the responsibility of the service layer (getConnections returns only safe fields).
129- // This test verifies the expected shape; the service-level tests in
130- // unified-calendars-freebusy.service.spec.ts verify no extra fields are returned.
131- expect ( serialized ) . toContain ( '"connectionId"' ) ;
132- } ) ;
133-
134- it ( "service getConnections should only return connectionId, type, email — no credential key" , async ( ) => {
135- // Verify the service contract: getConnections returns ONLY safe fields
136- const safeConnections = [ { connectionId : "1" , type : "google" as const , email : "user@gmail.com" } ] ;
137- mockFreebusyService . getConnections . mockResolvedValue ( safeConnections ) ;
138-
139118 const result = await controller . listConnections ( userId ) ;
140119 const conn = result . data . connections [ 0 ] ;
120+ const serialized = JSON . stringify ( result ) ;
141121
142- expect ( Object . keys ( conn ) ) . toEqual ( expect . arrayContaining ( [ "connectionId" , "type" , "email" ] ) ) ;
122+ // Core fields are present
123+ expect ( conn ) . toHaveProperty ( "connectionId" , "1" ) ;
124+ expect ( conn ) . toHaveProperty ( "type" , "google" ) ;
125+ expect ( conn ) . toHaveProperty ( "email" , "user@gmail.com" ) ;
126+ // key is stripped by the controller's destructuring — this is the key assertion
143127 expect ( conn ) . not . toHaveProperty ( "key" ) ;
144- expect ( conn ) . not . toHaveProperty ( "access_token" ) ;
145- expect ( JSON . stringify ( result ) ) . not . toContain ( "SECRET" ) ;
128+ expect ( serialized ) . not . toContain ( "SECRET_TOKEN" ) ;
129+ expect ( serialized ) . not . toContain ( "SECRET_REFRESH" ) ;
130+ // Only the 3 safe fields exist
131+ expect ( Object . keys ( conn ) ) . toEqual ( [ "connectionId" , "type" , "email" ] ) ;
146132 } ) ;
147133 } ) ;
148134
0 commit comments