211 lines
7.2 KiB
Dart
211 lines
7.2 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http/testing.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:siro_maps/core/services/device_fingerprint_service.dart';
|
|
import 'package:siro_maps/core/services/telemetry_tracking_service.dart';
|
|
import 'package:siro_maps/data/repositories/map_saas_repository.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
late TelemetryTrackingService telemetryService;
|
|
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({});
|
|
telemetryService = TelemetryTrackingService.instance;
|
|
telemetryService.resetForTesting();
|
|
DeviceFingerprintService.instance.setMockState(
|
|
fingerprint: 'siro_test_hw_unit_test_device_123',
|
|
);
|
|
});
|
|
|
|
group('TelemetryTrackingService Sampling & Stationary Filter Tests', () {
|
|
test('Samples position only if at least 3 seconds have passed', () {
|
|
final baseTime = DateTime(2026, 9, 15, 12, 0, 0);
|
|
|
|
// First point at T=0
|
|
final p1 = telemetryService.recordPosition(
|
|
latitude: 31.9539,
|
|
longitude: 35.9106,
|
|
speedKmH: 45.0,
|
|
heading: 90.0,
|
|
now: baseTime,
|
|
);
|
|
expect(p1, isTrue);
|
|
expect(telemetryService.bufferedPointCount, equals(1));
|
|
|
|
// Attempt second point after only 1 second (T=1s) -> Should be rejected
|
|
final p2 = telemetryService.recordPosition(
|
|
latitude: 31.9540,
|
|
longitude: 35.9107,
|
|
speedKmH: 46.0,
|
|
heading: 90.0,
|
|
now: baseTime.add(const Duration(seconds: 1)),
|
|
);
|
|
expect(p2, isFalse);
|
|
expect(telemetryService.bufferedPointCount, equals(1));
|
|
|
|
// Attempt third point after 3 seconds (T=3s) -> Should be accepted
|
|
final p3 = telemetryService.recordPosition(
|
|
latitude: 31.9545,
|
|
longitude: 35.9110,
|
|
speedKmH: 48.0,
|
|
heading: 92.0,
|
|
now: baseTime.add(const Duration(seconds: 3)),
|
|
);
|
|
expect(p3, isTrue);
|
|
expect(telemetryService.bufferedPointCount, equals(2));
|
|
});
|
|
|
|
test('Smart stationary filter suppresses redundant points when vehicle is stopped', () {
|
|
final baseTime = DateTime(2026, 9, 15, 12, 0, 0);
|
|
|
|
// Stop at intersection: initial point (speed = 0)
|
|
final initial = telemetryService.recordPosition(
|
|
latitude: 31.9500,
|
|
longitude: 35.9100,
|
|
speedKmH: 0.0,
|
|
heading: 0.0,
|
|
now: baseTime,
|
|
);
|
|
expect(initial, isTrue);
|
|
expect(telemetryService.bufferedPointCount, equals(1));
|
|
|
|
// After 5 seconds: still at same spot (speed = 0, distance = 0) -> filter out
|
|
final duplicateAfter5s = telemetryService.recordPosition(
|
|
latitude: 31.9500,
|
|
longitude: 35.9100,
|
|
speedKmH: 0.0,
|
|
heading: 0.0,
|
|
now: baseTime.add(const Duration(seconds: 5)),
|
|
);
|
|
expect(duplicateAfter5s, isFalse);
|
|
expect(telemetryService.bufferedPointCount, equals(1));
|
|
|
|
// After 35 seconds: still at same spot -> 30s heartbeat triggers!
|
|
final heartbeatAfter35s = telemetryService.recordPosition(
|
|
latitude: 31.9500,
|
|
longitude: 35.9100,
|
|
speedKmH: 0.0,
|
|
heading: 0.0,
|
|
now: baseTime.add(const Duration(seconds: 35)),
|
|
);
|
|
expect(heartbeatAfter35s, isTrue);
|
|
expect(telemetryService.bufferedPointCount, equals(2));
|
|
});
|
|
});
|
|
|
|
group('Telemetry Batch Upload & Offline Buffer Tests', () {
|
|
test('Flushes batched telemetry to server in a single request', () async {
|
|
List<dynamic> receivedPayloadPoints = [];
|
|
|
|
final mockClient = MockClient((request) async {
|
|
if (request.url.path.contains('/api/telemetry/batch')) {
|
|
final body = jsonDecode(request.body) as Map<String, dynamic>;
|
|
receivedPayloadPoints = body['points'] as List<dynamic>;
|
|
return http.Response(jsonEncode({'success': true, 'count': receivedPayloadPoints.length}), 201);
|
|
}
|
|
return http.Response('Not Found', 404);
|
|
});
|
|
|
|
final mockRepo = MapSaasRepository(client: mockClient);
|
|
|
|
// Record 3 points spaced by 3 seconds
|
|
final baseTime = DateTime(2026, 9, 15, 12, 0, 0);
|
|
telemetryService.recordPosition(
|
|
latitude: 31.951,
|
|
longitude: 35.911,
|
|
speedKmH: 30,
|
|
heading: 45,
|
|
now: baseTime,
|
|
);
|
|
telemetryService.recordPosition(
|
|
latitude: 31.952,
|
|
longitude: 35.912,
|
|
speedKmH: 35,
|
|
heading: 45,
|
|
now: baseTime.add(const Duration(seconds: 3)),
|
|
);
|
|
telemetryService.recordPosition(
|
|
latitude: 31.953,
|
|
longitude: 35.913,
|
|
speedKmH: 40,
|
|
heading: 50,
|
|
now: baseTime.add(const Duration(seconds: 6)),
|
|
);
|
|
|
|
expect(telemetryService.bufferedPointCount, equals(3));
|
|
|
|
// Flush buffer to backend
|
|
final success = await telemetryService.flush(repository: mockRepo);
|
|
expect(success, isTrue);
|
|
expect(receivedPayloadPoints.length, equals(3));
|
|
// Buffer should be empty after successful flush
|
|
expect(telemetryService.bufferedPointCount, equals(0));
|
|
});
|
|
|
|
test('Retains points in offline buffer if batch upload encounters error', () async {
|
|
final offlineClient = MockClient((request) async {
|
|
return http.Response('Gateway Timeout', 504);
|
|
});
|
|
|
|
final mockRepo = MapSaasRepository(client: offlineClient);
|
|
|
|
telemetryService.recordPosition(
|
|
latitude: 31.951,
|
|
longitude: 35.911,
|
|
speedKmH: 20,
|
|
heading: 10,
|
|
now: DateTime(2026, 9, 15, 12, 0, 0),
|
|
);
|
|
|
|
expect(telemetryService.bufferedPointCount, equals(1));
|
|
|
|
final success = await telemetryService.flush(repository: mockRepo);
|
|
expect(success, isFalse);
|
|
// Points must still be retained for next attempt
|
|
expect(telemetryService.bufferedPointCount, equals(1));
|
|
});
|
|
});
|
|
|
|
group('10-Day Periodic Updates & Maintenance Tests', () {
|
|
test('Skips update if less than 10 days since last check', () async {
|
|
final now = DateTime(2026, 9, 15, 12, 0, 0);
|
|
final lastCheck = now.subtract(const Duration(days: 4)); // Only 4 days ago
|
|
|
|
SharedPreferences.setMockInitialValues({
|
|
TelemetryTrackingService.prefKeyLastPeriodicCheck: lastCheck.millisecondsSinceEpoch,
|
|
});
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final executed = await telemetryService.checkPeriodicUpdates(
|
|
now: now,
|
|
mockPrefs: prefs,
|
|
);
|
|
|
|
expect(executed, isFalse);
|
|
});
|
|
|
|
test('Executes maintenance cycle if 10 or more days elapsed', () async {
|
|
final now = DateTime(2026, 9, 15, 12, 0, 0);
|
|
final lastCheck = now.subtract(const Duration(days: 12)); // 12 days ago
|
|
|
|
SharedPreferences.setMockInitialValues({
|
|
TelemetryTrackingService.prefKeyLastPeriodicCheck: lastCheck.millisecondsSinceEpoch,
|
|
});
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final executed = await telemetryService.checkPeriodicUpdates(
|
|
now: now,
|
|
mockPrefs: prefs,
|
|
);
|
|
|
|
expect(executed, isTrue);
|
|
final updatedTimestamp = prefs.getInt(TelemetryTrackingService.prefKeyLastPeriodicCheck);
|
|
expect(updatedTimestamp, equals(now.millisecondsSinceEpoch));
|
|
});
|
|
});
|
|
}
|