diff --git a/src/validation/formats.ts b/src/validation/formats.ts index dff5ad8..f9f9a54 100644 --- a/src/validation/formats.ts +++ b/src/validation/formats.ts @@ -3,6 +3,12 @@ import { FormatRegistry } from "@sinclair/typebox"; /** * Registers the string formats this codebase uses with TypeBox. * + * Import this from any test that validates a schema carrying a `format` + * without standing up Elysia. `tests/edge_protocol.test.ts` did not, and so + * passed only because some *other* test file in the same run happened to + * import Elysia first and register the formats globally - run on its own it + * failed, which is the worst way for a contract test to behave. + * * TypeBox ships no format validators: `format` is a registry lookup, and an * *unregistered* format does not pass - it fails. So a schema written with * `format: "uuid"` and checked outside Elysia rejects every value, including @@ -28,6 +34,24 @@ if (!FormatRegistry.Has("uuid")) { FormatRegistry.Set("uuid", (value) => UUID.test(value)); } +if (!FormatRegistry.Has("uri")) { + // `PrinterCommand.start_print.url` is the only user, and it carries a + // presigned object-store GET. Lenient on purpose: anything the platform + // parses as an absolute URL passes. A stricter pattern would have to + // anticipate whatever query parameters a signer adds, and rejecting a URL + // R2 would have honoured costs a print for no security - the command is + // minted by this backend, not submitted by a client. + FormatRegistry.Set("uri", (value) => { + try { + // Rejects a relative reference, which `format: "uri"` is supposed to: + // the two-argument form would happily resolve "../x" against a base. + return new URL(value).protocol.length > 0; + } catch { + return false; + } + }); +} + if (!FormatRegistry.Has("date-time")) { // Deliberately lenient: anything `Date.parse` understands. The producers // are `new Date().toISOString()` in TypeScript and chrono's RFC 3339 in diff --git a/tests/edge_protocol.test.ts b/tests/edge_protocol.test.ts index 4fba01d..367cd23 100644 --- a/tests/edge_protocol.test.ts +++ b/tests/edge_protocol.test.ts @@ -1,6 +1,12 @@ import { describe, expect, it } from "bun:test"; import { Value } from "@sinclair/typebox/value"; import { EdgeClientMessageSchema, EdgeServerMessageSchema } from "@continuum/ts-types"; +// Registers `uuid` and `uri`. Not optional: TypeBox treats an unregistered +// format as a failure, so without this every `start_print` in this file is +// rejected for its job_id and url. This file used to rely on another test +// file importing Elysia first, which made it pass in a full run and fail on +// its own. +import "../src/validation/formats"; import currentCapture from "./fixtures/gateway_frames.json"; import legacyCapture from "./fixtures/gateway_frames_legacy_nulls.json";