Fix tracing path and display issues

This commit is contained in:
Janez T
2026-03-12 14:08:00 +01:00
parent b38a8c55d6
commit 2d1e6c0ca4
11 changed files with 1021 additions and 89 deletions

View File

@@ -161,15 +161,23 @@ void main() {
expect(selection.mode, PathSelectionMode.flood);
});
test('received public byte path is added to history', () async {
final service = PathHistoryService();
await service.initialize();
await service.recordReceivedBytePath('abc123', [0x01, 0x02, 0x03], 3);
test(
'received public byte path is reversed before adding to history',
() async {
final service = PathHistoryService();
await service.initialize();
await service.recordReceivedBytePath('abc123', [
0x01,
0x02,
0x03,
0x04,
], 2);
final history = service.historyFor('abc123');
expect(history.directPaths, hasLength(1));
expect(history.directPaths.single.pathBytes, [0x01, 0x02, 0x03]);
expect(history.directPaths.single.hashSize, 3);
expect(history.directPaths.single.hopCount, 1);
});
final history = service.historyFor('abc123');
expect(history.directPaths, hasLength(1));
expect(history.directPaths.single.pathBytes, [0x03, 0x04, 0x01, 0x02]);
expect(history.directPaths.single.hashSize, 2);
expect(history.directPaths.single.hopCount, 2);
},
);
}

View File

@@ -116,6 +116,21 @@ void main() {
expect(resolved.matchCount, 2);
});
});
group('LogRxRouteDecoder.reverseHopBytes', () {
test('reverses path by hop size', () {
final reversed = LogRxRouteDecoder.reverseHopBytes([
0xc2,
0xba,
0x5f,
0xde,
0xaa,
0xbb,
], hashSize: 2);
expect(reversed, [0xaa, 0xbb, 0x5f, 0xde, 0xc2, 0xba]);
});
});
}
Contact _contact({required String name, required List<int> keyPrefix}) {

View File

@@ -0,0 +1,80 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:latlong2/latlong.dart';
import 'package:meshcore_sar_app/services/mesh_map_nodes_service.dart';
import 'package:meshcore_sar_app/utils/trace_node_resolver.dart';
void main() {
test(
'prefers closest local repeater over online fallback for shared prefix',
() {
final localNear = _node(
name: 'Near Local',
publicKey: 'aa1100',
latitude: 46.05,
longitude: 14.50,
);
final localFar = _node(
name: 'Far Local',
publicKey: 'aa2200',
latitude: 46.40,
longitude: 14.90,
);
final online = _node(
name: 'Online',
publicKey: 'aa3300',
latitude: 46.06,
longitude: 14.51,
);
final resolved = TraceNodeResolver.resolveBest(
nodes: [localNear, localFar, online],
localPublicKeys: {localNear.publicKey, localFar.publicKey},
prefixHex: 'aa',
referenceA: const LatLng(46.0, 14.5),
referenceB: const LatLng(46.1, 14.5),
);
expect(resolved.node?.name, 'Near Local');
expect(resolved.usedOnlineFallback, isFalse);
expect(resolved.matchCount, 2);
expect(resolved.matchSummary, '2 local matches');
},
);
test('falls back to online node only when local match is missing', () {
final online = _node(
name: 'Online Only',
publicKey: 'bb1100',
latitude: 46.06,
longitude: 14.51,
);
final resolved = TraceNodeResolver.resolveBest(
nodes: [online],
localPublicKeys: const {},
prefixHex: 'bb',
referenceA: const LatLng(46.0, 14.5),
referenceB: const LatLng(46.1, 14.5),
);
expect(resolved.node?.name, 'Online Only');
expect(resolved.usedOnlineFallback, isTrue);
expect(resolved.matchCount, 1);
});
}
MeshMapNode _node({
required String name,
required String publicKey,
required double latitude,
required double longitude,
}) {
return MeshMapNode(
type: 1,
name: name,
publicKey: publicKey,
latitude: latitude,
longitude: longitude,
updatedAtMs: 1,
);
}

View File

@@ -0,0 +1,85 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:meshcore_sar_app/l10n/app_localizations.dart';
import 'package:meshcore_sar_app/models/contact.dart';
import 'package:meshcore_sar_app/providers/connection_provider.dart';
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
import 'package:meshcore_sar_app/providers/map_provider.dart';
import 'package:meshcore_sar_app/providers/messages_provider.dart';
import 'package:meshcore_sar_app/providers/sensors_provider.dart';
import 'package:meshcore_sar_app/widgets/contacts/contact_tile.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
Contact buildContact({
required String name,
required ContactType type,
int secondByte = 1,
}) {
final publicKey = Uint8List(32);
publicKey[1] = secondByte;
return Contact(
publicKey: publicKey,
type: type,
flags: 0,
outPathLen: 0,
outPath: Uint8List(64),
advName: name,
lastAdvert: DateTime.now().millisecondsSinceEpoch ~/ 1000,
advLat: 46562000,
advLon: 14950000,
lastMod: DateTime.now().millisecondsSinceEpoch ~/ 1000,
);
}
Future<void> pumpTile(WidgetTester tester, Contact contact) async {
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ConnectionProvider()),
ChangeNotifierProvider(create: (_) => ContactsProvider()),
ChangeNotifierProvider(create: (_) => MessagesProvider()),
ChangeNotifierProvider(create: (_) => SensorsProvider()),
ChangeNotifierProvider(create: (_) => MapProvider()),
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(body: ContactTile(contact: contact)),
),
),
);
}
testWidgets('shows trace action for non-channel contacts', (tester) async {
await pumpTile(
tester,
buildContact(name: 'John Smith', type: ContactType.chat),
);
await tester.tap(find.text('John Smith'));
await tester.pumpAndSettle();
expect(find.text('Trace'), findsOneWidget);
});
testWidgets('does not show trace action for channels', (tester) async {
await pumpTile(
tester,
buildContact(name: 'Ops', type: ContactType.channel, secondByte: 3),
);
await tester.tap(find.text('Ops'));
await tester.pumpAndSettle();
expect(find.text('Trace'), findsNothing);
});
}