diff --git a/open-sse/executors/chipotle.ts b/open-sse/executors/chipotle.ts index 59391c4add..e01daef023 100644 --- a/open-sse/executors/chipotle.ts +++ b/open-sse/executors/chipotle.ts @@ -1,3 +1,5 @@ +import { randomInt, randomUUID } from "node:crypto"; + import { BaseExecutor, type ExecuteInput } from "./base.ts"; import type { ProviderCredentials } from "./base.ts"; import { sanitizeErrorMessage } from "../utils/error.ts"; @@ -6,14 +8,15 @@ const BASE_URL = "https://amelia.chipotle.com"; const DOMAIN_CODE = "chipotle"; const DOMAIN_ID = "23700760-e1e5-4c3c-931d-8804e29a6775"; -function randomServerId(): string { - return String(Math.floor(Math.random() * 1000)).padStart(3, "0"); +// Exported for unit testing — these run at WS-connect time, so a missing +// node:crypto import (crypto.randomInt is NOT on the Web Crypto global) would +// otherwise only surface as a runtime crash deep in _connect(). +export function randomServerId(): string { + return String(randomInt(0, 1000)).padStart(3, "0"); } -function randomSessionId(): string { - return Array.from({ length: 8 }, () => - Math.random().toString(36).substring(2, 6), - ).join(""); +export function randomSessionId(): string { + return randomUUID().replace(/-/g, "").slice(0, 32); } interface AmeliaSession { diff --git a/tests/unit/chipotle-executor.test.ts b/tests/unit/chipotle-executor.test.ts index 3354cf7b2b..07a093cd01 100644 --- a/tests/unit/chipotle-executor.test.ts +++ b/tests/unit/chipotle-executor.test.ts @@ -1,6 +1,10 @@ import { describe, it } from "node:test"; import assert from "node:assert"; -import { ChipotleExecutor } from "../../open-sse/executors/chipotle.ts"; +import { + ChipotleExecutor, + randomServerId, + randomSessionId, +} from "../../open-sse/executors/chipotle.ts"; const executor = new ChipotleExecutor(); @@ -12,7 +16,24 @@ describe("ChipotleExecutor", () => { it("buildUrl returns Amelia endpoint", () => { const url = executor.buildUrl("pepper-1", false); - assert.ok(url.includes("amelia.chipotle.com")); + const parsed = new URL(url); + assert.strictEqual(parsed.hostname, "amelia.chipotle.com"); + }); + + // Regression guard for the node:crypto import — randomInt is NOT on the Web + // Crypto global, so a bare `crypto.randomInt` would throw at WS-connect time. + it("randomServerId yields a 3-digit numeric string (crypto.randomInt available)", () => { + for (let i = 0; i < 50; i++) { + const id = randomServerId(); + assert.match(id, /^\d{3}$/, `expected 3 digits, got "${id}"`); + assert.ok(Number(id) >= 0 && Number(id) <= 999); + } + }); + + it("randomSessionId yields 32 hex chars (crypto.randomUUID available)", () => { + const id = randomSessionId(); + assert.match(id, /^[0-9a-f]{32}$/, `expected 32 hex chars, got "${id}"`); + assert.notStrictEqual(randomSessionId(), randomSessionId()); }); it("transformRequest passes model through", () => {