mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-03-23 03:13:53 +00:00
Detect config drift in cached tile providers and replace stale instances
When a user edits a tile type's URL template, max zoom, or API key without changing IDs, the cached DeflockTileProvider would keep the old frozen config. Now _getOrCreateProvider() computes a config fingerprint and replaces the provider when drift is detected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -359,8 +359,8 @@ void main() {
|
||||
|
||||
group('ProviderTileCacheStore eviction', () {
|
||||
/// Helper: populate cache with [count] tiles, each [bytesPerTile] bytes.
|
||||
/// Uses small delays between writes so modification times are
|
||||
/// distinguishable for oldest-modified ordering.
|
||||
/// Sets deterministic modification times (1 second apart) so eviction
|
||||
/// ordering is stable across platforms without relying on wall-clock delays.
|
||||
Future<void> fillCache(
|
||||
ProviderTileCacheStore store, {
|
||||
required int count,
|
||||
@@ -373,14 +373,22 @@ void main() {
|
||||
lastModified: null,
|
||||
etag: null,
|
||||
);
|
||||
final baseTime = DateTime.utc(2026, 1, 1);
|
||||
for (var i = 0; i < count; i++) {
|
||||
await store.putTile(
|
||||
url: 'https://tile.example.com/$prefix$i.png',
|
||||
metadata: metadata,
|
||||
bytes: bytes,
|
||||
);
|
||||
// Small delay so modification times are distinguishable for eviction order
|
||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
// Set deterministic mtime so eviction order is stable across platforms.
|
||||
final key = ProviderTileCacheStore.keyFor(
|
||||
'https://tile.example.com/$prefix$i.png',
|
||||
);
|
||||
final tileFile = File(p.join(store.cacheDirectory, '$key.tile'));
|
||||
final metaFile = File(p.join(store.cacheDirectory, '$key.meta'));
|
||||
final mtime = baseTime.add(Duration(seconds: i));
|
||||
await tileFile.setLastModified(mtime);
|
||||
await metaFile.setLastModified(mtime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import 'package:deflockapp/models/tile_provider.dart' as models;
|
||||
import 'package:deflockapp/services/deflock_tile_provider.dart';
|
||||
import 'package:deflockapp/widgets/map/tile_layer_manager.dart';
|
||||
|
||||
@@ -419,6 +420,136 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('TileLayerManager config drift detection', () {
|
||||
late TileLayerManager manager;
|
||||
|
||||
setUp(() {
|
||||
manager = TileLayerManager();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
manager.dispose();
|
||||
});
|
||||
|
||||
models.TileProvider makeProvider({String? apiKey}) => models.TileProvider(
|
||||
id: 'test_provider',
|
||||
name: 'Test',
|
||||
apiKey: apiKey,
|
||||
tileTypes: [],
|
||||
);
|
||||
|
||||
models.TileType makeTileType({
|
||||
String urlTemplate = 'https://example.com/{z}/{x}/{y}.png',
|
||||
int maxZoom = 18,
|
||||
}) =>
|
||||
models.TileType(
|
||||
id: 'test_tile',
|
||||
name: 'Test',
|
||||
urlTemplate: urlTemplate,
|
||||
attribution: 'Test',
|
||||
maxZoom: maxZoom,
|
||||
);
|
||||
|
||||
test('returns same provider for identical config', () {
|
||||
final provider = makeProvider();
|
||||
final tileType = makeTileType();
|
||||
|
||||
final layer1 = manager.buildTileLayer(
|
||||
selectedProvider: provider,
|
||||
selectedTileType: tileType,
|
||||
) as TileLayer;
|
||||
|
||||
final layer2 = manager.buildTileLayer(
|
||||
selectedProvider: provider,
|
||||
selectedTileType: tileType,
|
||||
) as TileLayer;
|
||||
|
||||
expect(
|
||||
identical(layer1.tileProvider, layer2.tileProvider),
|
||||
isTrue,
|
||||
reason: 'Same config should return the cached provider instance',
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces provider when urlTemplate changes', () {
|
||||
final provider = makeProvider();
|
||||
final tileTypeV1 = makeTileType(
|
||||
urlTemplate: 'https://old.example.com/{z}/{x}/{y}.png',
|
||||
);
|
||||
final tileTypeV2 = makeTileType(
|
||||
urlTemplate: 'https://new.example.com/{z}/{x}/{y}.png',
|
||||
);
|
||||
|
||||
final layer1 = manager.buildTileLayer(
|
||||
selectedProvider: provider,
|
||||
selectedTileType: tileTypeV1,
|
||||
) as TileLayer;
|
||||
|
||||
final layer2 = manager.buildTileLayer(
|
||||
selectedProvider: provider,
|
||||
selectedTileType: tileTypeV2,
|
||||
) as TileLayer;
|
||||
|
||||
expect(
|
||||
identical(layer1.tileProvider, layer2.tileProvider),
|
||||
isFalse,
|
||||
reason: 'Changed urlTemplate should create a new provider',
|
||||
);
|
||||
expect(
|
||||
(layer2.tileProvider as DeflockTileProvider).tileType.urlTemplate,
|
||||
'https://new.example.com/{z}/{x}/{y}.png',
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces provider when apiKey changes', () {
|
||||
final providerV1 = makeProvider(apiKey: 'old_key');
|
||||
final providerV2 = makeProvider(apiKey: 'new_key');
|
||||
final tileType = makeTileType();
|
||||
|
||||
final layer1 = manager.buildTileLayer(
|
||||
selectedProvider: providerV1,
|
||||
selectedTileType: tileType,
|
||||
) as TileLayer;
|
||||
|
||||
final layer2 = manager.buildTileLayer(
|
||||
selectedProvider: providerV2,
|
||||
selectedTileType: tileType,
|
||||
) as TileLayer;
|
||||
|
||||
expect(
|
||||
identical(layer1.tileProvider, layer2.tileProvider),
|
||||
isFalse,
|
||||
reason: 'Changed apiKey should create a new provider',
|
||||
);
|
||||
expect(
|
||||
(layer2.tileProvider as DeflockTileProvider).apiKey,
|
||||
'new_key',
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces provider when maxZoom changes', () {
|
||||
final provider = makeProvider();
|
||||
final tileTypeV1 = makeTileType(maxZoom: 18);
|
||||
final tileTypeV2 = makeTileType(maxZoom: 20);
|
||||
|
||||
final layer1 = manager.buildTileLayer(
|
||||
selectedProvider: provider,
|
||||
selectedTileType: tileTypeV1,
|
||||
) as TileLayer;
|
||||
|
||||
final layer2 = manager.buildTileLayer(
|
||||
selectedProvider: provider,
|
||||
selectedTileType: tileTypeV2,
|
||||
) as TileLayer;
|
||||
|
||||
expect(
|
||||
identical(layer1.tileProvider, layer2.tileProvider),
|
||||
isFalse,
|
||||
reason: 'Changed maxZoom should create a new provider',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('TileLayerManager error-type filtering', () {
|
||||
late TileLayerManager manager;
|
||||
late MockTileImage mockTile;
|
||||
|
||||
Reference in New Issue
Block a user