@@ -98,7 +98,15 @@ void main() {
9898
9999 final list = await LocalDb .instance.getConnections ();
100100 final loaded = list.singleWhere ((c) => c.id == id);
101- expect (loaded.password, 'redis-secret' );
101+ // Secrets are resolved lazily on demand, not eagerly in getConnections()
102+ expect (loaded.password, isNull);
103+
104+ final hydrated = await LocalDb .instance.hydrateConnection (loaded);
105+ expect (hydrated.password, 'redis-secret' );
106+
107+ final eagerlyHydrated = (await LocalDb .instance.getConnections (hydrateSecrets: true ))
108+ .singleWhere ((c) => c.id == id);
109+ expect (eagerlyHydrated.password, 'redis-secret' );
102110 });
103111
104112 test ('removeConnection deletes secure-store entries' , () async {
@@ -146,7 +154,7 @@ void main() {
146154 );
147155 await LocalDb .instance.updateConnection (updatedRow);
148156
149- final list = await LocalDb .instance.getConnections ();
157+ final list = await LocalDb .instance.getConnections (hydrateSecrets : true );
150158 final loaded = list.singleWhere ((c) => c.id == id);
151159 expect (loaded.name, 'PG_Updated' );
152160 expect (loaded.host, 'db.example.com' );
@@ -234,7 +242,7 @@ void main() {
234242 throwsA (isA <StateError >()),
235243 );
236244
237- final list = await LocalDb .instance.getConnections ();
245+ final list = await LocalDb .instance.getConnections (hydrateSecrets : true );
238246 final loaded = list.singleWhere ((c) => c.id == id);
239247 expect (loaded.name, 'PG_Before' );
240248 expect (loaded.host, 'localhost' );
@@ -273,10 +281,65 @@ void main() {
273281 expect (merged.host, 'db.example.com' );
274282
275283 await LocalDb .instance.updateConnection (merged);
276- final loaded = (await LocalDb .instance.getConnections ())
284+ final loaded = (await LocalDb .instance.getConnections (hydrateSecrets : true ))
277285 .singleWhere ((c) => c.id == id);
278286 expect (loaded.password, 'keep-me' );
279287 expect (loaded.name, 'PG Renamed' );
280288 });
289+
290+ test ('getConnections does not read secure store by default' , () async {
291+ const row = ConnectionRow (
292+ type: 'mysql' ,
293+ name: 'MySQL_Lazy' ,
294+ host: 'localhost' ,
295+ port: 3306 ,
296+ username: 'root' ,
297+ password: 'super-secret-pw' ,
298+ createdAt: '2026-01-01T00:00:00Z' ,
299+ );
300+ final id = await LocalDb .instance.addConnection (row);
301+
302+ // failNextRead should NOT be triggered because getConnections() does not read secrets!
303+ testMemorySecrets.failNextRead = StateError ('should not be called' );
304+
305+ final list = await LocalDb .instance.getConnections ();
306+ final conn = list.singleWhere ((c) => c.id == id);
307+ expect (conn.name, 'MySQL_Lazy' );
308+ expect (conn.password, isNull);
309+
310+ // failNextRead is still set because read was never called
311+ expect (testMemorySecrets.failNextRead, isNotNull);
312+ testMemorySecrets.failNextRead = null ;
313+ });
314+
315+ test (
316+ 'readForConnection and _hydrateConnection handle Keychain error gracefully without throwing' ,
317+ () async {
318+ const row = ConnectionRow (
319+ type: 'mysql' ,
320+ name: 'MySQL_Faulty' ,
321+ host: 'localhost' ,
322+ port: 3306 ,
323+ username: 'root' ,
324+ password: 'secret-password' ,
325+ createdAt: '2026-01-01T00:00:00Z' ,
326+ );
327+ final id = await LocalDb .instance.addConnection (row);
328+
329+ testMemorySecrets.failNextRead = StateError ('org.freedesktop.DBus.Error.NoReply' );
330+
331+ // readForConnection should return nulls instead of throwing
332+ final secrets = await ConnectionSecretsStore .readForConnection (id);
333+ expect (secrets.password, isNull);
334+ expect (secrets.connectionString, isNull);
335+
336+ testMemorySecrets.failNextRead = StateError ('Keychain locked' );
337+
338+ // getConnections(hydrateSecrets: true) should still succeed and return the connection row
339+ final list = await LocalDb .instance.getConnections (hydrateSecrets: true );
340+ final conn = list.singleWhere ((c) => c.id == id);
341+ expect (conn.name, 'MySQL_Faulty' );
342+ expect (conn.password, isNull);
343+ });
281344 });
282345}
0 commit comments