From 0b5f4ea36fc3f4ff783d047aab04fe214eec30e4 Mon Sep 17 00:00:00 2001 From: Strycher Date: Thu, 13 Aug 2026 01:26:20 -0400 Subject: [PATCH] fix(#678): distinguish CoreScope not-found from unreachable (#571) CoreScopeService.fetchCounts returns a three-state CoreScopeResult { found | notFound | unreachable } instead of a nullable count. Reachable-but-empty -> notFound ('Not seen by CoreScope yet'); network/HTTP/parse error -> unreachable. Channel-chat tap-refresh banner switches on status; adaptive poll reads .counts. Epic #677. Closes #571. Co-Authored-By: Claude Opus 4.8 --- lib/connector/meshcore_connector.dart | 38 ++++---- lib/l10n/app_en.arb | 1 + lib/l10n/app_localizations.dart | 6 ++ lib/l10n/app_localizations_bg.dart | 3 + lib/l10n/app_localizations_de.dart | 3 + lib/l10n/app_localizations_en.dart | 3 + lib/l10n/app_localizations_es.dart | 3 + lib/l10n/app_localizations_fr.dart | 3 + lib/l10n/app_localizations_hu.dart | 3 + lib/l10n/app_localizations_it.dart | 3 + lib/l10n/app_localizations_ja.dart | 3 + lib/l10n/app_localizations_ko.dart | 3 + lib/l10n/app_localizations_nl.dart | 3 + lib/l10n/app_localizations_pl.dart | 3 + lib/l10n/app_localizations_pt.dart | 3 + lib/l10n/app_localizations_ru.dart | 3 + lib/l10n/app_localizations_sk.dart | 3 + lib/l10n/app_localizations_sl.dart | 3 + lib/l10n/app_localizations_sv.dart | 3 + lib/l10n/app_localizations_uk.dart | 3 + lib/l10n/app_localizations_zh.dart | 3 + lib/screens/channel_chat_screen.dart | 17 ++-- lib/services/corescope_service.dart | 44 ++++++--- test/services/corescope_service_test.dart | 111 ++++++++++++---------- untranslated.json | 17 ++++ 25 files changed, 202 insertions(+), 86 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 52048b0..c4d0197 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -4948,7 +4948,9 @@ class MeshCoreConnector extends ChangeNotifier { for (final secs in _coreScopePollSecs) { await Future.delayed(Duration(seconds: secs)); if (!isConnected) return; - final counts = await _coreScopeService.fetchCounts(pkt.hashHex); + final counts = (await _coreScopeService.fetchCounts( + pkt.hashHex, + )).counts; if (counts == null) continue; if (counts.observers > best) { best = counts.observers; @@ -4979,26 +4981,28 @@ class MeshCoreConnector extends ChangeNotifier { } /// Tap-to-refresh: re-query CoreScope for a message's stored on-air hash and - /// bump the counts if they grew (they only ever climb). Returns the fetched - /// counts (or null on failure / no stored hash) so the UI can confirm. - Future refreshCoreScopeCounts( + /// bump the counts if they grew (they only ever climb). Returns the full + /// result so the UI can distinguish found / notFound / unreachable (#571). + Future refreshCoreScopeCounts( int channelIndex, String messageId, String hashHex, ) async { - final counts = await _coreScopeService.fetchCounts(hashHex); - if (counts == null) return null; - _updateChannelMessageById(channelIndex, messageId, (m) { - final current = m.coreScopeObserverCount ?? 0; - return counts.observers >= current - ? m.copyWith( - coreScopeObserverCount: counts.observers, - coreScopeObservationCount: counts.observations, - ) - : m; - }); - notifyListeners(); - return counts; + final result = await _coreScopeService.fetchCounts(hashHex); + final counts = result.counts; + if (counts != null) { + _updateChannelMessageById(channelIndex, messageId, (m) { + final current = m.coreScopeObserverCount ?? 0; + return counts.observers >= current + ? m.copyWith( + coreScopeObserverCount: counts.observers, + coreScopeObservationCount: counts.observations, + ) + : m; + }); + notifyListeners(); + } + return result; } void _updateChannelMessageById( diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 248b112..f261cfe 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -891,6 +891,7 @@ }, "channel_coreScopeRefresh": "Refresh CoreScope observers", "channel_coreScopeRefreshFailed": "CoreScope unreachable", + "channel_coreScopeNotFound": "Not seen by CoreScope yet", "channel_coreScopeCounts": "{observers, plural, =1{1 observer} other{{observers} observers}} ยท {observations} observations", "@channel_coreScopeCounts": { "placeholders": { diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index c886449..e65dafa 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3130,6 +3130,12 @@ abstract class AppLocalizations { /// **'CoreScope unreachable'** String get channel_coreScopeRefreshFailed; + /// No description provided for @channel_coreScopeNotFound. + /// + /// In en, this message translates to: + /// **'Not seen by CoreScope yet'** + String get channel_coreScopeNotFound; + /// No description provided for @channel_coreScopeCounts. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index b0cf93b..80e3466 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1734,6 +1734,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index a55a6ac..fce03a9 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1732,6 +1732,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0415d17..f8ecb85 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1703,6 +1703,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 7bd6e11..4d82563 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1729,6 +1729,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 4d514b2..ee474ca 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1736,6 +1736,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 676dafb..ec9b32c 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -1741,6 +1741,9 @@ class AppLocalizationsHu extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 5e9bb80..4aaf0e2 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1731,6 +1731,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 566fa32..256d8eb 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1666,6 +1666,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 0321b79..16cfb15 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1662,6 +1662,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index f6dd647..e3ea207 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1719,6 +1719,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 08c0a80..b3f47e7 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1744,6 +1744,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index e091edf..65a2002 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1728,6 +1728,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 3a543c3..926ee26 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1732,6 +1732,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 0cba92f..74549d2 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index b7bbd5a..a4e3abe 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1717,6 +1717,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index b8e4588..4badf0b 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1712,6 +1712,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 4c6569b..7b68e19 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1727,6 +1727,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 39fff5d..0206520 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1641,6 +1641,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get channel_coreScopeRefreshFailed => 'CoreScope unreachable'; + @override + String get channel_coreScopeNotFound => 'Not seen by CoreScope yet'; + @override String channel_coreScopeCounts(int observers, int observations) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 09132f3..9f4d167 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -27,6 +27,7 @@ import '../models/contact.dart'; import '../models/translation_support.dart'; import '../models/app_settings.dart'; import '../services/app_settings_service.dart'; +import '../services/corescope_service.dart'; import '../widgets/channel_notify_mode.dart'; import '../services/block_service.dart'; import '../services/chat_text_scale_service.dart'; @@ -1920,17 +1921,21 @@ class _ChannelChatScreenState extends State { if (hash == null) return; final connector = context.read(); final l10n = context.l10n; - final counts = await connector.refreshCoreScopeCounts( + final result = await connector.refreshCoreScopeCounts( message.channelIndex ?? _currentChannel.index, message.messageId, hash, ); if (!mounted) return; - _showCoreScopeBanner( - counts == null - ? l10n.channel_coreScopeRefreshFailed - : l10n.channel_coreScopeCounts(counts.observers, counts.observations), - ); + final text = switch (result.status) { + CoreScopeStatus.found => l10n.channel_coreScopeCounts( + result.counts!.observers, + result.counts!.observations, + ), + CoreScopeStatus.notFound => l10n.channel_coreScopeNotFound, + CoreScopeStatus.unreachable => l10n.channel_coreScopeRefreshFailed, + }; + _showCoreScopeBanner(text); } /// Top banner (out of the way of the composer), auto-dismissed after a few diff --git a/lib/services/corescope_service.dart b/lib/services/corescope_service.dart index 617b04b..5dc4672 100644 --- a/lib/services/corescope_service.dart +++ b/lib/services/corescope_service.dart @@ -17,6 +17,25 @@ class CoreScopeCounts { final int observations; } +/// Outcome of a CoreScope query, so the UI can tell "no record of this packet +/// yet" apart from "couldn't reach CoreScope" (#571). +enum CoreScopeStatus { found, notFound, unreachable } + +class CoreScopeResult { + const CoreScopeResult(this.status, [this.counts]); + final CoreScopeStatus status; + final CoreScopeCounts? counts; + + static const CoreScopeResult notFound = CoreScopeResult( + CoreScopeStatus.notFound, + ); + static const CoreScopeResult unreachable = CoreScopeResult( + CoreScopeStatus.unreachable, + ); + factory CoreScopeResult.found(CoreScopeCounts counts) => + CoreScopeResult(CoreScopeStatus.found, counts); +} + /// Queries a CoreScope instance for how many observers reported a given packet, /// keyed by the firmware/mesh packet hash (#524). Read-only and best-effort: /// any failure (offline, timeout, non-200, bad body, unknown packet) returns @@ -35,10 +54,10 @@ class CoreScopeService { final Duration timeout; /// Distinct observers and total observations for [packetHash] (16 lowercase - /// hex chars). Returns null on any error, or when CoreScope has no record of - /// the hash yet. - Future fetchCounts(String packetHash) async { - if (packetHash.isEmpty) return null; + /// hex chars). `notFound` = CoreScope reachable but has no record of the hash + /// yet; `unreachable` = network/HTTP/parse error. Never throws. + Future fetchCounts(String packetHash) async { + if (packetHash.isEmpty) return CoreScopeResult.unreachable; final uri = Uri( scheme: useTls ? 'https' : 'http', host: host, @@ -57,19 +76,20 @@ class CoreScopeService { 'HTTP ${resp.statusCode} for hash $packetHash', tag: 'CoreScope', ); - return null; + return CoreScopeResult.unreachable; } final body = jsonDecode(resp.body); - if (body is! Map) return null; + if (body is! Map) return CoreScopeResult.unreachable; final packets = body['packets']; - if (packets is! List || packets.isEmpty) { + if (packets is! List) return CoreScopeResult.unreachable; + if (packets.isEmpty) { appLogger.info('no record yet for $packetHash', tag: 'CoreScope'); - return null; + return CoreScopeResult.notFound; } final first = packets.first; - if (first is! Map) return null; + if (first is! Map) return CoreScopeResult.unreachable; final observers = first['observer_count']; - if (observers is! num) return null; + if (observers is! num) return CoreScopeResult.unreachable; final observations = first['observation_count']; final counts = CoreScopeCounts( observers: observers.toInt(), @@ -82,10 +102,10 @@ class CoreScopeService { 'for $packetHash', tag: 'CoreScope', ); - return counts; + return CoreScopeResult.found(counts); } catch (e) { appLogger.warn('Query failed for $packetHash: $e', tag: 'CoreScope'); - return null; + return CoreScopeResult.unreachable; } } diff --git a/test/services/corescope_service_test.dart b/test/services/corescope_service_test.dart index fd141df..47abf8e 100644 --- a/test/services/corescope_service_test.dart +++ b/test/services/corescope_service_test.dart @@ -5,87 +5,96 @@ import 'package:meshcore_open/services/corescope_service.dart'; void main() { group('CoreScopeService.fetchCounts', () { - test( - 'parses observer + observation counts from a grouped response', - () async { - late Uri captured; - final service = CoreScopeService( - host: 'map.okimesh.org', - client: MockClient((req) async { - captured = req.url; - return http.Response( - '{"packets":[{"hash":"abc123","observer_count":15,' - '"observation_count":34}],"total":1}', - 200, - ); - }), - ); + test('found: parses observer + observation counts', () async { + late Uri captured; + final service = CoreScopeService( + host: 'map.okimesh.org', + client: MockClient((req) async { + captured = req.url; + return http.Response( + '{"packets":[{"hash":"abc123","observer_count":15,' + '"observation_count":34}],"total":1}', + 200, + ); + }), + ); - final counts = await service.fetchCounts('abc123'); - expect(counts, isNotNull); - expect(counts!.observers, 15); - expect(counts.observations, 34); - expect(captured.host, 'map.okimesh.org'); - expect(captured.path, '/api/packets'); - expect(captured.queryParameters['hash'], 'abc123'); - expect(captured.queryParameters['groupByHash'], 'true'); - }, - ); + final result = await service.fetchCounts('abc123'); + expect(result.status, CoreScopeStatus.found); + expect(result.counts!.observers, 15); + expect(result.counts!.observations, 34); + expect(captured.host, 'map.okimesh.org'); + expect(captured.path, '/api/packets'); + expect(captured.queryParameters['hash'], 'abc123'); + expect(captured.queryParameters['groupByHash'], 'true'); + }); - test('falls back to observers when observation_count is absent', () async { + test('found: observations falls back to observers when absent', () async { final service = CoreScopeService( client: MockClient( (_) async => http.Response('{"packets":[{"observer_count":9}]}', 200), ), ); - final counts = await service.fetchCounts('abc123'); - expect(counts!.observers, 9); - expect(counts.observations, 9); + final result = await service.fetchCounts('abc123'); + expect(result.status, CoreScopeStatus.found); + expect(result.counts!.observers, 9); + expect(result.counts!.observations, 9); }); - test('returns null when CoreScope has no record (empty packets)', () async { + test('notFound: reachable but empty packets (the #571 case)', () async { final service = CoreScopeService( client: MockClient( (_) async => http.Response('{"packets":[],"total":0}', 200), ), ); - expect(await service.fetchCounts('deadbeef'), isNull); + final result = await service.fetchCounts('deadbeef'); + expect(result.status, CoreScopeStatus.notFound); + expect(result.counts, isNull); }); - test('returns null on non-200', () async { + test('unreachable on non-200', () async { final service = CoreScopeService( client: MockClient((_) async => http.Response('nope', 503)), ); - expect(await service.fetchCounts('abc123'), isNull); + expect( + (await service.fetchCounts('abc123')).status, + CoreScopeStatus.unreachable, + ); }); - test('returns null on malformed body', () async { + test('unreachable on malformed body', () async { final service = CoreScopeService( client: MockClient((_) async => http.Response('not json', 200)), ); - expect(await service.fetchCounts('abc123'), isNull); + expect( + (await service.fetchCounts('abc123')).status, + CoreScopeStatus.unreachable, + ); }); - test('returns null (never throws) on transport failure', () async { + test('unreachable (never throws) on transport failure', () async { final service = CoreScopeService( client: MockClient((_) async => throw Exception('offline')), ); - expect(await service.fetchCounts('abc123'), isNull); + expect( + (await service.fetchCounts('abc123')).status, + CoreScopeStatus.unreachable, + ); }); - test( - 'returns null for an empty hash without hitting the network', - () async { - var called = false; - final service = CoreScopeService( - client: MockClient((_) async { - called = true; - return http.Response('{}', 200); - }), - ); - expect(await service.fetchCounts(''), isNull); - expect(called, isFalse); - }, - ); + test('empty hash: unreachable without hitting the network', () async { + var called = false; + final service = CoreScopeService( + client: MockClient((_) async { + called = true; + return http.Response('{}', 200); + }), + ); + expect( + (await service.fetchCounts('')).status, + CoreScopeStatus.unreachable, + ); + expect(called, isFalse); + }); }); } diff --git a/untranslated.json b/untranslated.json index 2f8061a..b27cf49 100644 --- a/untranslated.json +++ b/untranslated.json @@ -59,6 +59,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -205,6 +206,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -351,6 +353,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -497,6 +500,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -643,6 +647,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -789,6 +794,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -935,6 +941,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -1081,6 +1088,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -1227,6 +1235,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -1373,6 +1382,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -1519,6 +1529,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -1665,6 +1676,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -1811,6 +1823,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -1957,6 +1970,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -2103,6 +2117,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -2249,6 +2264,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint", @@ -2395,6 +2411,7 @@ "channel_coreScopeTooltip", "channel_coreScopeRefresh", "channel_coreScopeRefreshFailed", + "channel_coreScopeNotFound", "channel_coreScopeCounts", "channel_senderNameUnknown", "channel_senderNameUnknownHint",