road distance: pin-fingerprint guard so embedded/live/cached never cross-pollute
The road-distance cache was keyed purely by station ID with no coordinate, so
a value routed to one pin could be served for a same-ID station whose stored
coordinate came from a different source (live fetch vs bundled offline dump
vs a corrected pin), reproducing Maps mismatches.
- RoadDistanceCache now stores each routed pin (CachedRoadDistance{meters,
lat, lng}) and roadDistanceMeters() only serves a value when the displayed
station's coordinate matches the pinned one (within ~11 m).
- Kept the existing origin-distance staleness guard.
- Verified embedded sample data (ids y1/se1/...) never collides with real
relay IDs, and the widget STRICT radius filter drops far-offline samples, so
no actual leak existed in practice — this closes the theoretical stale-pin
channel and future-proofs against coordinate fixes.
This commit is contained in:
@@ -647,7 +647,8 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
func testRoadDistanceUsedWhenCachedNear() {
|
||||
let s = station("a", 51.5074, -0.1278)
|
||||
// Cache a road distance of 3.2 km for this station from the user's fix.
|
||||
FuelStore.saveRoadDistances(sourceLat: 51.6, sourceLng: -0.1, entries: ["a": 3200])
|
||||
FuelStore.saveRoadDistances(sourceLat: 51.6, sourceLng: -0.1,
|
||||
entries: ["a": .init(meters: 3200, lat: 51.5074, lng: -0.1278)])
|
||||
let km = FuelStore.displayDistanceKM(station: s, userLat: 51.6, userLng: -0.1)
|
||||
XCTAssertEqual(km, 3.2, accuracy: 0.0001)
|
||||
}
|
||||
@@ -655,8 +656,9 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
func testRoadDistanceNilWhenOriginFar() {
|
||||
let s = station("a", 51.5074, -0.1278)
|
||||
// Cache built in London, but the user is now ~200 km away -> stale.
|
||||
FuelStore.saveRoadDistances(sourceLat: 51.5074, sourceLng: -0.1278, entries: ["a": 3200])
|
||||
let meters = FuelStore.roadDistanceMeters(for: "a", userLat: 53.4808, userLng: -2.2426)
|
||||
FuelStore.saveRoadDistances(sourceLat: 51.5074, sourceLng: -0.1278,
|
||||
entries: ["a": .init(meters: 3200, lat: 51.5074, lng: -0.1278)])
|
||||
let meters = FuelStore.roadDistanceMeters(for: s, userLat: 53.4808, userLng: -2.2426)
|
||||
XCTAssertNil(meters)
|
||||
// And display falls back to straight-line.
|
||||
let km = FuelStore.displayDistanceKM(station: s, userLat: 53.4808, userLng: -2.2426)
|
||||
@@ -664,12 +666,26 @@ final class RoadDistanceCacheTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRoadDistanceUsedForOtherStationNotFound() {
|
||||
FuelStore.saveRoadDistances(sourceLat: 51.6, sourceLng: -0.1, entries: ["a": 3200])
|
||||
FuelStore.saveRoadDistances(sourceLat: 51.6, sourceLng: -0.1,
|
||||
entries: ["a": .init(meters: 3200, lat: 51.5074, lng: -0.1278)])
|
||||
// A station that isn't in the cache falls back to straight-line.
|
||||
let s = station("z", 51.51, -0.13)
|
||||
let km = FuelStore.displayDistanceKM(station: s, userLat: 51.6, userLng: -0.1)
|
||||
XCTAssertEqual(km, s.distanceKM(to: 51.6, lng2: -0.1), accuracy: 0.0001)
|
||||
}
|
||||
|
||||
func testRoadDistanceNotServedWhenStationPinDiffers() {
|
||||
// Route a road distance to station "a" at pin P1.
|
||||
FuelStore.saveRoadDistances(sourceLat: 51.6, sourceLng: -0.1,
|
||||
entries: ["a": .init(meters: 3200, lat: 51.5074, lng: -0.1278)])
|
||||
// The SAME station id appears with a moved pin (corrected coordinate /
|
||||
// different embedded vs live source): the cached route to P1 must NOT
|
||||
// be served — it belongs to a different location.
|
||||
let moved = station("a", 51.5400, -0.1600)
|
||||
let km = FuelStore.displayDistanceKM(station: moved, userLat: 51.6, userLng: -0.1)
|
||||
XCTAssertEqual(km, moved.distanceKM(to: 51.6, lng2: -0.1), accuracy: 0.0001,
|
||||
"road value routed to the old pin leaked onto a different coordinate")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Install identity
|
||||
|
||||
Reference in New Issue
Block a user