Files
rea/src/domain/analysisView/environmentStateWriteValues.test.ts
T
Edgar ArakelyanandClaude Opus 5.5 cbb7b613f7 test(environment): cover write plans, the write gate and served writes
Unit tests for clock, UUID, token, slug and delimited-token values, the field
rules that explain created and updated entities, response shapes, insert
rules and the shared apply functions; view tests for every plan reason, delete
confirmation by a 400, 404 or null read, learned insert rules, unmodelled
writes and observations from later reads; services tests for served creates,
updates and deletes, refusals that leave the state unchanged, deterministic
values across reset and load, missing entities, the catalog and paging-only
list lines; and an MCP boundary test that serves a capture through the built
CLI, applies a create and resets it. The capture fixture records request
bodies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:58:26 -07:00

243 lines
7.9 KiB
TypeScript

import { expect, it } from "vitest";
import type { JsonValue } from "../jsonValue.js";
import {
containsDelimited,
delimitedOffsets,
formatClock,
integerOf,
isUuid,
readClock,
replaceDelimited,
seededToken,
seededUuid,
slugOf,
spelledLike,
type ClockFormat,
} from "./environmentStateWriteValues.js";
const WHOLE_SECOND = Date.UTC(2026, 2, 4, 5, 6, 7);
const WITH_MS = WHOLE_SECOND + 123;
const V4_UUID =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
const UUID_EXEMPLAR = "123e4567-e89b-12d3-a456-426614174000";
const iso = (
separator: "T" | " ",
fraction: number,
zone: string,
): ClockFormat => ({ kind: "iso", separator, fraction, zone });
const seeds = (count: number): string[] =>
Array.from({ length: count }, (_, index) => `seed-${String(index)}`);
it.each<[string, number, ClockFormat]>([
["2026-03-04T05:06:07Z", WHOLE_SECOND, iso("T", 0, "Z")],
["2026-03-04 05:06:07.123Z", WITH_MS, iso(" ", 3, "Z")],
["2026-03-04T05:06:07.123456Z", WITH_MS, iso("T", 6, "Z")],
["2026-03-04T07:06:07.123+02:00", WITH_MS, iso("T", 3, "+02:00")],
["2026-03-03T23:36:07-0530", WHOLE_SECOND, iso("T", 0, "-0530")],
["2026-03-04T05:06:07", WHOLE_SECOND, iso("T", 0, "")],
["2024-02-29 00:00:00Z", Date.UTC(2024, 1, 29), iso(" ", 0, "Z")],
])("reads the ISO clock %s with its spelling", (text, ms, format) => {
expect(readClock(text)).toEqual({ ms, format });
});
it.each([
"2026-02-30T00:00:00Z",
"2026-02-29T00:00:00Z",
"2026-13-01T00:00:00Z",
"2026-03-04T24:00:00Z",
"2026-03-04T05:60:00Z",
"2026-03-04T05:06:60Z",
"2026-03-04",
"2026-03-04T05:06Z",
"2026-03-04T05:06:07+05",
"2026-03-04T05:06:07.1234567890Z",
"2026-03-04t05:06:07Z",
"next tuesday",
])("refuses %s as a clock", (text) => {
expect(readClock(text)).toBeUndefined();
});
it.each<[JsonValue | undefined, ReturnType<typeof readClock>]>([
[
WHOLE_SECOND / 1000,
{ ms: WHOLE_SECOND, format: { kind: "epoch", unit: "s" } },
],
[WITH_MS, { ms: WITH_MS, format: { kind: "epoch", unit: "ms" } }],
[999_999_999, undefined],
[99_999_999_999, undefined],
[10_000_000_000_000, undefined],
[WHOLE_SECOND / 1000 + 0.5, undefined],
[String(WHOLE_SECOND / 1000), undefined],
[null, undefined],
[undefined, undefined],
])("reads the epoch value %j", (value, expected) => {
expect(readClock(value)).toEqual(expected);
});
it.each<JsonValue>([
"2026-03-04T05:06:07Z",
"2026-03-04 05:06:07.123Z",
"2026-03-04T07:06:07.123+02:00",
"2026-03-03T23:36:07-0530",
"2026-03-04T05:06:07.1",
"2026-03-04T05:06:07.123000000",
WHOLE_SECOND / 1000,
WITH_MS,
])("formats %j back to its own spelling", (value) => {
const clock = readClock(value)!;
expect(formatClock(clock.ms, clock.format)).toBe(value);
});
it.each<[ClockFormat, JsonValue]>([
[iso("T", 6, "Z"), "2026-03-04T05:06:07.123000Z"],
[iso(" ", 9, ""), "2026-03-04 05:06:07.123000000"],
[iso("T", 1, "Z"), "2026-03-04T05:06:07.1Z"],
[iso("T", 0, "+02:00"), "2026-03-04T07:06:07+02:00"],
[iso("T", 3, "-0530"), "2026-03-03T23:36:07.123-0530"],
[{ kind: "epoch", unit: "s" }, WHOLE_SECOND / 1000],
[{ kind: "epoch", unit: "ms" }, WITH_MS],
])("formats an instant as %j", (format, expected) => {
expect(formatClock(WITH_MS, format)).toBe(expected);
});
it.each<[JsonValue | undefined, boolean]>([
[UUID_EXEMPLAR, true],
[UUID_EXEMPLAR.toUpperCase(), true],
[UUID_EXEMPLAR.replaceAll("-", ""), false],
[UUID_EXEMPLAR.slice(1), false],
[`g${UUID_EXEMPLAR.slice(1)}`, false],
[` ${UUID_EXEMPLAR}`, false],
[42, false],
[undefined, false],
])("isUuid(%j) is %s", (value, expected) => {
expect(isUuid(value)).toBe(expected);
});
it.each<[JsonValue | undefined, number | undefined]>([
[42, 42],
[-3, -3],
["42", 42],
["007", 7],
["123456789012345", 123_456_789_012_345],
["1234567890123456", undefined],
["-3", undefined],
["4.2", undefined],
[4.2, undefined],
[2 ** 53, undefined],
["", undefined],
[" 4", undefined],
[true, undefined],
[null, undefined],
[undefined, undefined],
])("integerOf(%j) is %j", (value, expected) => {
expect(integerOf(value)).toBe(expected);
});
it("spells an integer as the recorded value spells integers", () => {
expect(spelledLike("7", 8)).toBe("8");
expect(spelledLike(7, 8)).toBe(8);
expect(spelledLike(null, 8)).toBe(8);
});
it.each([
["Hello World", "hello-world"],
["Héllo, Wörld!", "hello-world"],
[" Crème brûlée -- recipe__v2 ", "creme-brulee-recipe-v2"],
["Fullwidth 2", "fullwidth-2"],
["already-a-slug", "already-a-slug"],
["--Edge--", "edge"],
["!!!", ""],
])("slugOf(%j) is %j", (text, slug) => {
expect(slugOf(text)).toBe(slug);
});
it.each<[string, string, number[]]>([
["/issues/2", "2", [8]],
["2026", "2", []],
["2 of 22, page 2", "2", [0, 14]],
["x2/2y", "2", []],
["é2", "2", []],
["https://x.test/items/abc123?ref=abc123", "abc123", [21, 32]],
["anything", "", []],
])("finds %j's delimited %j at %j", (text, token, offsets) => {
expect(delimitedOffsets(text, token)).toEqual(offsets);
});
it("replaces only delimited occurrences", () => {
expect(replaceDelimited("/issues/2 in 2026", "2", "9")).toBe(
"/issues/9 in 2026",
);
expect(replaceDelimited("a-2-b-2", "2", "10")).toBe("a-10-b-10");
expect(replaceDelimited("2026", "2", "9")).toBe("2026");
});
// Known bug: a rejected match skips its whole length, hiding an overlapping delimited one.
it("finds a delimited match that overlaps an undelimited one", () => {
expect(delimitedOffsets("11-1-1", "1-1")).toEqual([3]);
});
it("finds a delimited token in any string nested in objects and arrays", () => {
expect(
containsDelimited({ a: [1, { b: ["x", "see /issues/2"] }] }, "2"),
).toBe(true);
expect(containsDelimited("2", "2")).toBe(true);
expect(containsDelimited({ c: "2026", n: 2, d: null }, "2")).toBe(false);
expect(containsDelimited(["2026", true, [{ e: "x2" }]], "2")).toBe(false);
});
it("derives a deterministic version 4 UUID per seed", () => {
const first = seededUuid("seed-a", UUID_EXEMPLAR);
expect(first).toMatch(V4_UUID);
expect(seededUuid("seed-a", UUID_EXEMPLAR)).toBe(first);
const derived = seeds(64).map((seed) => seededUuid(seed, UUID_EXEMPLAR));
expect(new Set(derived).size).toBe(derived.length);
for (const uuid of derived) expect(uuid).toMatch(V4_UUID);
});
it("keeps upper case only when the recorded UUID is upper case", () => {
const upper = seededUuid("seed-a", UUID_EXEMPLAR.toUpperCase());
expect(upper).toBe(seededUuid("seed-a", UUID_EXEMPLAR).toUpperCase());
expect(upper).toMatch(/[A-F]/u);
const digits = "12345678-1234-4234-8234-123456789012";
expect(seededUuid("seed-a", digits)).toMatch(V4_UUID);
const mixed = "123e4567-E89B-12d3-a456-426614174000";
expect(seededUuid("seed-a", mixed)).toMatch(V4_UUID);
});
it.each<[string, RegExp]>([
["a1b2c3d4e5f6", /^[0-9a-f]{12}$/u],
["A1B2C3D4E5F6", /^[0-9A-F]{12}$/u],
["k9x2m7q4z1w8", /^[0-9a-z]{12}$/u],
["AbCdEfGhIjKl", /^[A-Za-z]{12}$/u],
["deadbeefcafe", /^[a-z]{12}$/u],
[
"tok_9Xa-7bQ.2",
/^[0-9A-Za-z]{3}_[0-9A-Za-z]{3}-[0-9A-Za-z]{3}\.[0-9A-Za-z]$/u,
],
["____", /^____$/u],
])("keeps the length and character classes of %j", (exemplar, shape) => {
for (const seed of seeds(16)) {
const token = seededToken(seed, exemplar);
expect(token).toHaveLength(exemplar.length);
expect(token).toMatch(shape);
expect(seededToken(seed, exemplar)).toBe(token);
}
});
it("draws tokens from the whole alphabet the recorded token uses", () => {
const characters = (exemplar: string): string =>
seeds(64)
.map((seed) => seededToken(seed, exemplar))
.join("");
expect(characters("a1b2c3d4e5f6")).toMatch(/^[0-9a-f]+$/u);
expect(characters("k9x2m7q4z1w8")).toMatch(/[g-z]/u);
const mixed = characters("AbCdEfGhIjKl");
expect(mixed).toMatch(/[a-z]/u);
expect(mixed).toMatch(/[A-Z]/u);
expect(seededToken("seed-a", "k9x2m7q4z1w8")).not.toBe(
seededToken("seed-b", "k9x2m7q4z1w8"),
);
});