diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index c41c26f7..c48dedd6 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -3957,6 +3957,11 @@ class MeshCoreConnector extends ChangeNotifier { _contactSyncDecisionPending = false; _recoveryDelivered.clear(); _recoveryGone.clear(); + // A complete sync ends any recovery still running, so a leftover by-key + // loop cannot report a shortfall against the cleared union. (Gemini) + _recoveryRun++; + _recoveryCheckingKeys = false; + _recoverySettleTimer?.cancel(); return; } diff --git a/test/connector/contact_sync_test.dart b/test/connector/contact_sync_test.dart index 867230cf..8752e3f6 100644 --- a/test/connector/contact_sync_test.dart +++ b/test/connector/contact_sync_test.dart @@ -418,6 +418,30 @@ void main() { expect(names(), {'Alpha', 'Bravo', 'Charlie'}); }); + test( + 'a complete sync during the by-key checks ends the recovery', + () async { + seedSaved(); + final alpha = [_contactFrame(0x11, 'Alpha')]; + await connector.getContacts(); + await answerStream(350, alpha); + await answerStream(350, alpha); + await answerStream(350, alpha); + // The by-key checks are running; a refresh now comes back complete. + await connector.getContacts(); + await answerStream(3, [ + _contactFrame(0x11, 'Alpha'), + _contactFrame(0x22, 'Bravo'), + _contactFrame(0x33, 'Charlie'), + ]); + await Future.delayed(const Duration(milliseconds: 100)); + + expect(connector.contactSyncShortfall, isNull); + expect(connector.contactSyncDecisionPending, isFalse); + expect(names(), {'Alpha', 'Bravo', 'Charlie'}); + }, + ); + test('the banner cannot be dismissed while a decision is open', () async { await threeShortSyncs(); connector.dismissContactSyncShortfall();