Files
tiennm99 04925cf789 fix(api): keep the daily pick through upstream blips, mint every session id
A timeout or 5xx from Mapillary no longer moves the day to another
panorama; only a missing image does, and the pick is written with SET NX
so instances that draw differently still serve one. /api/daily maps a dry
pool to 404 and an upstream failure to 502.

/api/new-game always mints a fresh session id instead of reusing the
client's, which let a new round be overwritten mid-guess; /api/guess
rejects ids the server could not have minted. The route reuses the history
it already read, runs round statistics alongside the board writes, and
drops an unreachable error branch. One cookie parser serves the player id
and the debug key; the leaderboard fan-out takes points, not a function,
and no longer rewraps errors.

Tests share one Mapillary stub, the leaderboard stub contract compares a
real scored row, and scanKeys and SET NX are covered.
2026-09-28 15:01:41 +07:00

41 lines
1.5 KiB
JavaScript

import { afterEach, beforeEach, vi } from 'vitest';
// A Mapillary Graph API that resolves any image id, installed around every
// test of the file that calls it. Everything else passes through -- against a
// real Redis the Upstash client speaks over fetch too, and swallowing its calls
// would fail every session write rather than exercising the route.
/**
* Stub Mapillary for every test in the calling file, with a token set.
* @param {(id: string) => Response|undefined} [override] Called per lookup;
* a Response it returns replaces the default image answer.
* @returns {void}
*/
export function stubMapillary(override) {
const originalToken = process.env.MAPILLARY_ACCESS_TOKEN;
beforeEach(() => {
process.env.MAPILLARY_ACCESS_TOKEN = 'test-token';
const realFetch = globalThis.fetch;
vi.stubGlobal('fetch', async (url, init) => {
if (!String(url).includes('graph.mapillary.com')) return realFetch(url, init);
const id = String(url).split('/').pop().split('?')[0];
return override?.(id) ?? new Response(
JSON.stringify({
id,
thumb_2048_url: `https://example.invalid/${id}.jpg`,
is_pano: true,
geometry: { coordinates: [106.7, 10.77] },
}),
{ status: 200 }
);
});
});
afterEach(() => {
vi.unstubAllGlobals();
if (originalToken === undefined) delete process.env.MAPILLARY_ACCESS_TOKEN;
else process.env.MAPILLARY_ACCESS_TOKEN = originalToken;
});
}