mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Merge upstream/main into pr/panoramax
CHANGELOG: keep both new [Unreleased] entries (Panoramax, and the emergency-transponder highlight from #856). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
de83e1c17f
131 files changed
+7123
-275
No files matched your search
@@ -24,7 +24,9 @@ export function capRows(rows, limit = DEFAULT_LIMIT) {
|
||||
|
||||
/** Format an epoch millisecond time as ISO 8601, or null. */
|
||||
export function isoTime(ms) {
|
||||
return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
|
||||
if (!Number.isFinite(ms)) return null;
|
||||
const date = new Date(ms);
|
||||
return Number.isFinite(date.getTime()) ? date.toISOString() : null;
|
||||
}
|
||||
|
||||
/** "1 earthquake", "3 earthquakes". */
|
||||
|
||||
+14
-4
@@ -106,10 +106,20 @@ export function validateValue(schema, value, path = 'arguments') {
|
||||
fail('must be a string');
|
||||
break;
|
||||
}
|
||||
if (schema.minLength != null && value.length < schema.minLength)
|
||||
fail(`must be at least ${schema.minLength} characters`);
|
||||
if (schema.maxLength != null && value.length > schema.maxLength)
|
||||
fail(`must be at most ${schema.maxLength} characters`);
|
||||
if (schema.minLength != null || schema.maxLength != null) {
|
||||
// Code points, as JSON Schema counts. Counting stops once both bounds
|
||||
// are decided, so a huge argument costs no more than its limit.
|
||||
const limit = Math.max(
|
||||
schema.minLength ?? 0,
|
||||
schema.maxLength != null ? schema.maxLength + 1 : 0,
|
||||
);
|
||||
let length = 0;
|
||||
for (const _ of value) if (++length >= limit) break;
|
||||
if (schema.minLength != null && length < schema.minLength)
|
||||
fail(`must be at least ${schema.minLength} characters`);
|
||||
if (schema.maxLength != null && length > schema.maxLength)
|
||||
fail(`must be at most ${schema.maxLength} characters`);
|
||||
}
|
||||
if (
|
||||
schema.pattern !== undefined &&
|
||||
!new RegExp(schema.pattern, 'u').test(value)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { validateValue } from './schema.js';
|
||||
test('tool string schemas count Unicode characters rather than UTF-16 units', () => {
|
||||
const value = '𠮷野家😀';
|
||||
assert.deepEqual(validateValue({ type: 'string', maxLength: 4 }, value), []);
|
||||
assert.equal(
|
||||
validateValue({ type: 'string', minLength: 5 }, value).length,
|
||||
1,
|
||||
);
|
||||
assert.equal(
|
||||
validateValue({ type: 'string', maxLength: 3 }, value).length,
|
||||
1,
|
||||
);
|
||||
assert.deepEqual(
|
||||
validateValue({ type: 'string', minLength: 4, maxLength: 4 }, 'abcd'),
|
||||
[],
|
||||
);
|
||||
assert.equal(validateValue({ type: 'string', minLength: 2 }, '𠮷').length, 1);
|
||||
});
|
||||
|
||||
test('tool string bounds hold at their edges and on huge arguments', () => {
|
||||
const string = (bounds) => ({ type: 'string', ...bounds });
|
||||
assert.deepEqual(validateValue(string({ maxLength: 3 }), '😀😀😀'), []);
|
||||
assert.equal(validateValue(string({ maxLength: 3 }), '😀😀😀😀').length, 1);
|
||||
assert.deepEqual(validateValue(string({ minLength: 3 }), '😀😀😀'), []);
|
||||
assert.deepEqual(validateValue(string({}), 'x'.repeat(1e6)), []);
|
||||
// Counting all of it takes roughly 0.7 s; stopping at the limit takes none.
|
||||
const huge = 'x'.repeat(1e8);
|
||||
const started = performance.now();
|
||||
assert.equal(validateValue(string({ maxLength: 60 }), huge).length, 1);
|
||||
assert.ok(performance.now() - started < 200, 'stops counting past the limit');
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { isoTime } from './results.js';
|
||||
import { getBikeShare } from './queries/mobility.js';
|
||||
test('out-of-range finite timestamps are unknown rather than throwing', async () => {
|
||||
for (const value of [1e30, -1e30, NaN, Infinity, null])
|
||||
assert.equal(isoTime(value), null);
|
||||
assert.equal(isoTime(0), '1970-01-01T00:00:00.000Z');
|
||||
assert.equal(isoTime(8640000000000000), '+275760-09-13T00:00:00.000Z');
|
||||
assert.equal(isoTime(-8640000000000000), '-271821-04-20T00:00:00.000Z');
|
||||
for (const value of [8640000000000001, -8640000000000001, -Infinity])
|
||||
assert.equal(isoTime(value), null);
|
||||
assert.equal(isoTime(-1), '1969-12-31T23:59:59.999Z');
|
||||
const station = {
|
||||
station_id: 's1',
|
||||
name: 'Fixture',
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
capacity: 4,
|
||||
};
|
||||
const result = await getBikeShare.run(
|
||||
{ area: { bbox: [-1, -1, 1, 1] } },
|
||||
{
|
||||
services: {
|
||||
bikeshare: {
|
||||
systems: [
|
||||
{
|
||||
id: 'fixture',
|
||||
city: 'Fixture',
|
||||
centerLat: 0,
|
||||
centerLon: 0,
|
||||
loadRadiusKm: 30,
|
||||
stationInformationUrl: 'information',
|
||||
stationStatusUrl: 'status',
|
||||
},
|
||||
],
|
||||
async getStations(url) {
|
||||
return {
|
||||
data: {
|
||||
stations:
|
||||
url === 'information'
|
||||
? [station]
|
||||
: [
|
||||
{
|
||||
station_id: 's1',
|
||||
last_reported: 1e30,
|
||||
num_bikes_available: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.equal(result.data.returned, 1);
|
||||
assert.equal(result.data.rows[0].last_reported, null);
|
||||
});
|
||||
Reference in new issue
Block a user