Files
deflock-app/test/services/offline_area_service_test.dart
Doug Borg 2d92214bed Add offline-first tile system with per-provider caching and error retry
- Add ServicePolicy framework with OSM-specific rate limiting and TTL
- Add per-provider disk tile cache (ProviderTileCacheStore) with O(1)
  lookup, oldest-modified eviction, and ETag/304 revalidation
- Rewrite DeflockTileProvider with two paths: common (NetworkTileProvider)
  and offline-first (disk cache -> local tiles -> network with caching)
- Add zoom-aware offline routing so tiles outside offline area zoom ranges
  use the efficient common path instead of the overhead-heavy offline path
- Fix HTTP client lifecycle: dispose() is now a no-op for flutter_map
  widget recycling; shutdown() handles permanent teardown
- Add TileLayerManager with exponential backoff retry (2s->60s cap),
  provider switch detection, and backoff reset
- Guard null provider/tileType in download dialog with localized error
- Fix Nominatim cache key to use normalized viewbox values
- Comprehensive test coverage (1800+ lines across 6 test files)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:34:01 -07:00

94 lines
3.3 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:latlong2/latlong.dart';
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
import 'package:deflockapp/services/offline_area_service.dart';
import 'package:deflockapp/services/offline_areas/offline_area_models.dart';
OfflineArea _makeArea({
String providerId = 'osm',
String tileTypeId = 'standard',
int minZoom = 5,
int maxZoom = 12,
OfflineAreaStatus status = OfflineAreaStatus.complete,
}) {
return OfflineArea(
id: 'test-$providerId-$tileTypeId-$minZoom-$maxZoom',
bounds: LatLngBounds(const LatLng(0, 0), const LatLng(1, 1)),
minZoom: minZoom,
maxZoom: maxZoom,
directory: '/tmp/test-area',
status: status,
tileProviderId: providerId,
tileTypeId: tileTypeId,
);
}
void main() {
final service = OfflineAreaService();
setUp(() {
service.setAreasForTesting([]);
});
group('hasOfflineAreasForProviderAtZoom', () {
test('returns true for zoom within range', () {
service.setAreasForTesting([_makeArea(minZoom: 5, maxZoom: 12)]);
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 5), isTrue);
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 8), isTrue);
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 12), isTrue);
});
test('returns false for zoom outside range', () {
service.setAreasForTesting([_makeArea(minZoom: 5, maxZoom: 12)]);
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 4), isFalse);
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 13), isFalse);
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 14), isFalse);
});
test('returns false for wrong provider', () {
service.setAreasForTesting([_makeArea(providerId: 'osm')]);
expect(service.hasOfflineAreasForProviderAtZoom('other', 'standard', 8), isFalse);
});
test('returns false for wrong tile type', () {
service.setAreasForTesting([_makeArea(tileTypeId: 'standard')]);
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'satellite', 8), isFalse);
});
test('returns false for non-complete areas', () {
service.setAreasForTesting([
_makeArea(status: OfflineAreaStatus.downloading),
_makeArea(status: OfflineAreaStatus.error),
]);
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 8), isFalse);
});
test('returns false when initialized with no areas', () {
service.setAreasForTesting([]);
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 8), isFalse);
});
test('matches when any area covers the zoom level', () {
service.setAreasForTesting([
_makeArea(minZoom: 5, maxZoom: 8),
_makeArea(minZoom: 10, maxZoom: 14),
]);
// In first area's range
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 6), isTrue);
// In gap between areas
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 9), isFalse);
// In second area's range
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 13), isTrue);
// Beyond both areas
expect(service.hasOfflineAreasForProviderAtZoom('osm', 'standard', 15), isFalse);
});
});
}