diff --git a/CHANGELOG.md b/CHANGELOG.md index dddcdeef8..9ab3b03cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,6 @@ - Tool string length validation counts Unicode characters correctly, including characters outside the basic multilingual plane. - Director timeline seeks cross the dateline along the short longitude arc. -- Recent Imagery refuses missing bounding-box edges instead of interpreting them as zero coordinates. - Recent Imagery’s exported `setBox` and `searchHls` reject missing, blank or boolean programmatic box edges instead of interpreting them as zero coordinates. diff --git a/src/layers/recentImagery/imagery-box-missing-edge.test.mjs b/src/layers/recentImagery/imagery-box-missing-edge.test.mjs index 3e5a8e8a5..698bcaee5 100644 --- a/src/layers/recentImagery/imagery-box-missing-edge.test.mjs +++ b/src/layers/recentImagery/imagery-box-missing-edge.test.mjs @@ -1,6 +1,13 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { validateBox, boxCentre } from './model.js'; +import { + boxCentre, + boxFromRectangle, + dequantizeBox, + validateBox, +} from './model.js'; +import { searchHls } from './catalog.js'; + test('missing imagery edges do not turn into invented zero coordinates', () => { for (const value of [null, undefined, '', ' ', false, true]) { const box = { west: value, south: 0, east: 1, north: 1 }; @@ -14,3 +21,28 @@ test('missing imagery edges do not turn into invented zero coordinates', () => { true, ); }); + +test('every edge and every box entry point refuses a missing edge', async () => { + const blanks = [null, undefined, '', ' ', false, [], {}]; + for (const edge of ['west', 'south', 'east', 'north']) { + for (const value of blanks) { + const box = { west: 1, south: 1, east: 2, north: 2, [edge]: value }; + assert.equal(validateBox(box).ok, false, `${edge}=${String(value)}`); + // Share-link integers and Cesium radians are converted per edge. + assert.equal(dequantizeBox(box), null, `share ${edge}`); + assert.equal(boxFromRectangle(box), null, `radians ${edge}`); + } + } + let fetched = false; + await assert.rejects( + searchHls({ + box: { west: ' ', south: 1, east: 2, north: 2 }, + fetchImpl: async () => { + fetched = true; + }, + }), + TypeError, + ); + assert.equal(fetched, false); + assert.ok(dequantizeBox({ west: 0, south: 0, east: 100000, north: 100000 })); +}); diff --git a/src/layers/recentImagery/model.js b/src/layers/recentImagery/model.js index 63c5ec16c..24ef54fc6 100644 --- a/src/layers/recentImagery/model.js +++ b/src/layers/recentImagery/model.js @@ -75,20 +75,23 @@ const pad2 = (value) => String(value).padStart(2, '0'); * east are not swapped: west > east crosses the dateline and `validateBox` * rejects it. Null when any edge is nonfinite. */ +/** A box edge as a number, or null when it is missing, blank or not numeric. */ +function edgeNumber(value) { + if (typeof value === 'number') return Number.isFinite(value) ? value : null; + if (typeof value !== 'string' || !value.trim()) return null; + const number = Number(value); + return Number.isFinite(number) ? number : null; +} + function normalizeBox(box) { if (!box || typeof box !== 'object') return null; - const edges = [box.west, box.south, box.east, box.north]; - if ( - edges.some( - (value) => - value == null || - typeof value === 'boolean' || - (typeof value === 'string' && !value.trim()), - ) - ) - return null; - const [west, south, east, north] = edges.map(Number); - if (![west, south, east, north].every(Number.isFinite)) return null; + const [west, south, east, north] = [ + box.west, + box.south, + box.east, + box.north, + ].map(edgeNumber); + if ([west, south, east, north].some((edge) => edge == null)) return null; return { west, south: Math.min(south, north), @@ -216,11 +219,16 @@ export function boxFromPin(lon, lat, sideKm = PIN_BOX_SIDE_KM) { function mapBox(box, fn) { if (!box || typeof box !== 'object') return null; + // Converting first would turn a blank edge into 0 before normalizeBox sees it. + const edge = (value) => { + const number = edgeNumber(value); + return number == null ? null : fn(number); + }; return normalizeBox({ - west: fn(box.west), - south: fn(box.south), - east: fn(box.east), - north: fn(box.north), + west: edge(box.west), + south: edge(box.south), + east: edge(box.east), + north: edge(box.north), }); }