mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
test(street-level): harden KartaView tests (mutation score 51% → 95%)
A mutation review (91, then 130 mutants) found tests that could not fail
(quick presses with synchronous fakes, a no-flight check with a provider
that never posed), and untested paths. New and rewritten tests kill 123 of
130 mutants; the 7 survivors are equivalent or harmless redraws.
- viewer: held-back loads for quick presses, stale sharp/flat loads,
close mid-load, ends checks for an older photo, WebGL retried per mount,
focus handed back, look direction kept on ›.
- provider: id-ordered pages with out-of-order indexes, page and probe
failures retried, overtaken lookups, cones re-centred on ‹ ›, leftover
imagery filters hide no line or cone, coverage hints, tile cache.
- source: 429 backoff and Retry-After under mock timers, hang timeouts,
the nearest query's parameters. policy: storage-host injection, FOV 360,
longitudes past ±180. panorama: a stub WebGL context (angles, lost
context, re-measuring). lineCoverage.test.mjs: tooHigh hint, passes().
- core: openImage({frame:false}) and openNearest leave the camera
(with a posing provider and a default-open control), KartaView's legend
and wording end to end; viewerHost supportsRenderModes.
- Fixed tick-count waits replaced by an until() poll.
- Browser gate fixture: odd photos archived (409 at storage2, served by
the proxy), the first lookup rate-limited (429, Retry-After: 0); the
line-click step asserts the camera stays and › skips the archived photo.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
bd0bbf5134
commit
3d4c6de21a
14 files changed
+1374
-81
No files matched your search
@@ -6,7 +6,11 @@ import { metresApart, photoJpeg } from './mapillaryGraph.mjs';
|
||||
* the gate's KartaView steps need no network. One drive runs north along
|
||||
* KV_LINE; every tenth photo is a 360° one. Photos live on storage2 while
|
||||
* their `fileurl*` name storage1, as KartaView's API does, so the gate also
|
||||
* proves the provider builds addresses from `storage`.
|
||||
* proves the provider builds addresses from `storage`. Every odd-indexed
|
||||
* photo is archived (storage answers 409, the image proxy still serves it),
|
||||
* so the nearest photo opened is even and › must skip the one after it. The
|
||||
* first nearest-photo lookup is rate-limited (429, Retry-After: 0), as
|
||||
* KartaView does under load.
|
||||
*/
|
||||
|
||||
export const KV_SEQUENCE_ID = '900001';
|
||||
@@ -21,7 +25,15 @@ const SHOT = '2024-05-02 16:20:00.000';
|
||||
|
||||
const CORS = Object.freeze({ 'Access-Control-Allow-Origin': '*' });
|
||||
|
||||
/** The drive's photos, south to north; every tenth is 360°. */
|
||||
/** Lookups answered so far: the first one is rate-limited. */
|
||||
let nearestLookups = 0;
|
||||
|
||||
/** Forget the lookups answered, so the next one is rate-limited again. */
|
||||
export function resetKartaViewFixture() {
|
||||
nearestLookups = 0;
|
||||
}
|
||||
|
||||
/** The drive's photos, south to north; every tenth is 360°, every odd one archived. */
|
||||
export function kartaViewPhotos() {
|
||||
const step = KV_SPACING_M / METRES_PER_DEG_LAT;
|
||||
const count = Math.floor((KV_LINE.north - KV_LINE.south) / step) + 1;
|
||||
@@ -31,6 +43,7 @@ export function kartaViewPhotos() {
|
||||
lon: KV_LINE.lon,
|
||||
lat: Number((KV_LINE.south + i * step).toFixed(7)),
|
||||
isPano: i % 10 === 5,
|
||||
archived: i % 2 === 1,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -127,10 +140,16 @@ export function answerKartaViewRequest({ method, url }) {
|
||||
return /^\/pr:sharp\/rs:fit:\d+:\d+\/[\w-]+$/.test(address.pathname)
|
||||
? image
|
||||
: { ...raw(404, {}), known: false };
|
||||
if (address.hostname === 'storage2.openstreetcam.org')
|
||||
return address.pathname.includes(KV_SEQUENCE_ID)
|
||||
? image
|
||||
: { ...raw(404, {}), known: false };
|
||||
if (address.hostname === 'storage2.openstreetcam.org') {
|
||||
if (!address.pathname.includes(KV_SEQUENCE_ID))
|
||||
return { ...raw(404, {}), known: false };
|
||||
const archived = photos.some(
|
||||
(photo) =>
|
||||
photo.archived && address.pathname.endsWith(`_${photo.id}.jpg`),
|
||||
);
|
||||
// An archived photo's file is gone from storage: KartaView answers 409.
|
||||
return archived ? { ...raw(409, {}), known: true } : image;
|
||||
}
|
||||
if (/^storage\d+\.openstreetcam\.org$/.test(address.hostname))
|
||||
// The wrong host the API names: KartaView answers 404 there.
|
||||
return { ...raw(404, {}), known: false };
|
||||
@@ -148,6 +167,16 @@ export function answerKartaViewRequest({ method, url }) {
|
||||
known: true,
|
||||
};
|
||||
if (path === '/2.0/photo/') {
|
||||
if (nearestLookups++ === 0)
|
||||
return {
|
||||
...raw(429, {}),
|
||||
headers: {
|
||||
...CORS,
|
||||
'Retry-After': '0',
|
||||
'Access-Control-Expose-Headers': 'Retry-After',
|
||||
},
|
||||
known: true,
|
||||
};
|
||||
const q = address.searchParams;
|
||||
const at = { lat: Number(q.get('lat')), lon: Number(q.get('lng')) };
|
||||
const near = photos
|
||||
|
||||
@@ -921,6 +921,7 @@
|
||||
"src/layers/streetLevel/providers/kartaview/panorama.test.mjs",
|
||||
"src/layers/streetLevel/providers/kartaview/viewer.test.mjs",
|
||||
"src/layers/streetLevel/providers/kartaview/provider.test.mjs",
|
||||
"src/layers/streetLevel/lineCoverage.test.mjs",
|
||||
"src/testSupport/kartaViewFakes.mjs",
|
||||
"src/testSupport/googleStreetViewFakes.mjs",
|
||||
"server/providers/mapillary.js",
|
||||
|
||||
@@ -850,7 +850,7 @@ async function main() {
|
||||
},
|
||||
);
|
||||
await step(
|
||||
'KartaView: a click on a line opens the nearest photo, cones follow its drive, and › steps along it',
|
||||
'KartaView: a click on a line opens the nearest photo where the camera is, cones follow its drive, and › steps along it, past an archived photo',
|
||||
async ({ skip }) => {
|
||||
const along = Array.from({ length: 9 }, (_, i) => [
|
||||
KV_LINE.lon,
|
||||
@@ -871,6 +871,17 @@ async function main() {
|
||||
if (!handle && !fixtures) return skip('no KartaView line in view');
|
||||
assert.ok(handle, 'a KartaView line is pickable');
|
||||
const at = await handle.jsonValue();
|
||||
const camera = () =>
|
||||
page.evaluate(() => {
|
||||
const c = window.__godsEyeView.viewer.camera.positionCartographic;
|
||||
return {
|
||||
lon: (c.longitude * 180) / Math.PI,
|
||||
lat: (c.latitude * 180) / Math.PI,
|
||||
height: c.height,
|
||||
};
|
||||
});
|
||||
const parked = await camera();
|
||||
// The first lookup is rate-limited (fixtures): it waits and retries.
|
||||
await page.mouse.click(at.x, at.y);
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
@@ -894,10 +905,22 @@ async function main() {
|
||||
}),
|
||||
'the photo is on screen',
|
||||
);
|
||||
// A line click picks a spot: the camera stays where it was.
|
||||
await sleep(1500);
|
||||
const now = await camera();
|
||||
assert.ok(
|
||||
Math.abs(now.lon - parked.lon) < 1e-6 &&
|
||||
Math.abs(now.lat - parked.lat) < 1e-6 &&
|
||||
Math.abs(now.height - parked.height) < 0.5,
|
||||
`the camera did not move: ${JSON.stringify({ parked, now })}`,
|
||||
);
|
||||
if (fixtures) {
|
||||
assert.ok(
|
||||
photos.some((photo) => photo.id === street.imageId),
|
||||
`${street.imageId} is a fixture photo`,
|
||||
const opened = photos.find((photo) => photo.id === street.imageId);
|
||||
assert.ok(opened, `${street.imageId} is a fixture photo`);
|
||||
assert.equal(
|
||||
opened.archived,
|
||||
false,
|
||||
'the nearest photo that storage still has',
|
||||
);
|
||||
assert.ok(
|
||||
await uiUntil(
|
||||
@@ -922,12 +945,21 @@ async function main() {
|
||||
),
|
||||
'› opened the next photo',
|
||||
);
|
||||
if (fixtures)
|
||||
if (fixtures) {
|
||||
const skipped = photos.find(
|
||||
(photo) => photo.id === String(Number(before) + 1),
|
||||
);
|
||||
assert.equal(
|
||||
skipped?.archived,
|
||||
true,
|
||||
'the photo after is archived',
|
||||
);
|
||||
assert.equal(
|
||||
(await ui()).street.imageId,
|
||||
String(Number(before) + 1),
|
||||
'the next photo along the drive',
|
||||
String(Number(before) + 2),
|
||||
'› skipped the archived photo to the next one along the drive',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (fixtures)
|
||||
|
||||
Reference in new issue
Block a user