fix(osm): key cached place searches to the Overpass instance that answered

The /api/osm/features cache key was the query alone, kept for a day, so after
an operator repointed OVERPASS_UPSTREAMS the old instance's answers were still
served. The key now includes the configured-upstream tag, as main's other
Overpass caches check it, so a new instance is always asked.
This commit is contained in:
Manjunath committed 2026-11-03 17:27:36 +05:30
1 parent 474cce51eb
commit dfdbb134d5
2 files changed
+43 -4

No files matched your search

+34 -1
View File
@@ -158,7 +158,10 @@ test('answers are compacted: centres, kept tags, truncation and counts', () => {
assert.equal(count.count, 5);
});
function mount(fetchPayload, { featuresCache, configured = () => true } = {}) {
function mount(
fetchPayload,
{ featuresCache, configured = () => true, upstreamTag } = {},
) {
const routes = new Map();
const writes = [];
const memoryCache = () => {
@@ -177,6 +180,7 @@ function mount(fetchPayload, { featuresCache, configured = () => true } = {}) {
fetchPayload,
cacheRoot: null,
configured,
...(upstreamTag ? { upstreamTag } : {}),
featuresCache: featuresCache || memoryCache(),
},
);
@@ -254,6 +258,35 @@ test('the route answers, caches, and runs one upstream query at a time', async (
assert.equal(refused.body.code, 'AREA_TOO_LARGE');
});
test('a cached answer is served only by the Overpass instance that gave it', async () => {
let tag = 'instance-a';
let upstream = 0;
const { request } = mount(
async () => {
upstream += 1;
return {
status: 200,
body: JSON.stringify({
elements: [{ type: 'node', id: 7, lat: 1, lon: 1, tags: {} }],
}),
};
},
{ upstreamTag: () => tag },
);
assert.equal((await request('preset=hospital&bbox=0,0,2,2')).status, 200);
const hit = await request('preset=hospital&bbox=0,0,2,2');
assert.equal(hit.headers['X-OSM-Cache'], 'HIT');
assert.equal(upstream, 1);
tag = 'instance-b';
const switched = await request('preset=hospital&bbox=0,0,2,2');
assert.equal(switched.headers['X-OSM-Cache'], 'MISS');
assert.equal(
upstream,
2,
'the new instance is asked, not the old answer served',
);
});
test('an upstream refusal is not an empty answer', async () => {
const { request } = mount(async () => ({
status: 429,