Files
rea/src/application/environment/environmentStateWriteReview.test.ts
T

676 lines
22 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
timedCaptureEvidence,
analysisViewCaptureEvidence,
type TimedCaptureRow,
} from "../../../tests/fixtures/analysisView.js";
import {
environmentStateServeFixture,
environmentStateServeInputs,
} from "../../../tests/fixtures/environmentServe.js";
import { servedCatalog } from "./environmentState.js";
import { servedFrom } from "../../../tests/fixtures/environmentStateServing.js";
import {
BOOK_NOT_FOUND,
FIRST_BOOK,
SECOND_BOOK,
book,
bookClock,
renamedBook,
offsetListed,
} from "../../../tests/fixtures/environmentStateWrites.js";
import { webNetworkCaptureSchema } from "../../domain/webNetworkCapture.js";
const archived = (value: boolean) => ({ ...FIRST_BOOK, archived: value });
describe("bodyless action evidence", () => {
it.each<{
name: string;
postData: unknown;
bodySize?: unknown;
served?: boolean;
shape?: boolean;
}>([
{
name: "form",
postData: {
mimeType: "application/x-www-form-urlencoded",
text: "toggle=false",
},
},
{
name: "malformed JSON",
postData: { mimeType: "application/json", text: "{" },
},
{
name: "oversized JSON",
postData: {
mimeType: "application/json",
text: JSON.stringify({ note: "x".repeat(256 * 1024) }),
},
},
{ name: "unretained body", postData: undefined },
{
name: "omitted body with zero size",
postData: undefined,
bodySize: 0,
served: true,
},
{ name: "explicit null with zero size", postData: null, bodySize: 0 },
{
name: "params-only with zero size",
postData: {
mimeType: "application/json",
params: [{ name: "archived", value: "true" }],
},
bodySize: 0,
},
{
name: "empty text with nonempty params",
postData: {
mimeType: "application/json",
text: "",
params: [{ name: "archived", value: "true" }],
},
bodySize: 0,
},
{
name: "explicit empty body with zero size",
postData: { mimeType: "application/json", text: "", params: [] },
bodySize: 0,
served: true,
},
{
name: "invalid size with retained JSON",
postData: { mimeType: "application/json", text: '{"archived":true}' },
bodySize: "unknown",
shape: true,
},
])(
"preserves literal request-body availability for repeated actions: $name",
({ postData, bodySize = 12, served = false, shape = false }) => {
const capture = timedCaptureEvidence([
["GET", "/api/books", [archived(false), SECOND_BOOK]],
["GET", "/api/books/1", archived(false)],
["GET", "/api/books/2", SECOND_BOOK],
["POST", "/api/books/1/archive", archived(true)],
["GET", "/api/books/1", archived(true)],
["POST", "/api/books/1/archive", archived(true)],
["GET", "/api/books/1", archived(true)],
]);
const result = capture.normalized_result as {
records: {
reported: { request: { postData?: unknown; bodySize?: unknown } };
}[];
};
const request = result.records[5]!.reported.request;
request.bodySize = bodySize;
if (postData === undefined) delete request.postData;
else request.postData = postData;
if (shape) {
result.records[3]!.reported.request.bodySize = bodySize;
result.records[3]!.reported.request.postData = postData;
}
const { source, state } = environmentStateServeInputs(
environmentStateServeFixture(
analysisViewCaptureEvidence(
webNetworkCaptureSchema.parse(capture.normalized_result),
),
),
);
expect(state.writes.get("post_books_archive_by_id")).toMatchObject({
served,
});
const tool = source.manifest.tools.find(
({ name }) => name === "post_books_archive_by_id",
);
expect(tool).toBeDefined();
if (shape)
expect(tool?.body_shape).toMatchObject({
properties: { archived: "boolean" },
});
else if (bodySize === 0) expect(tool?.body_shape).toBeNull();
},
);
});
it("keeps a stored array-of-object field as an ordinary update body", () => {
const original = { ...FIRST_BOOK, tags: [{ id: 7, name: "Old" }] };
const second = { ...SECOND_BOOK, tags: [{ id: 8, name: "Other" }] };
const changed = { ...original, tags: [{ id: 9, name: "Recorded" }] };
const books = servedFrom([
["GET", "/api/books", [original, second]],
["GET", "/api/books/1", original],
["GET", "/api/books/2", second],
["PATCH", "/api/books/1", changed, 200, { tags: changed.tags }],
["GET", "/api/books/1", changed],
["GET", "/api/books", [changed, second]],
]);
expect(books.state.writes.get("patch_books_by_id")).toMatchObject({
served: true,
});
expect(books.state.writes.get("patch_books_by_id")?.body).not.toHaveProperty(
"envelope",
);
const tags = [
{ id: 10, name: "Caller" },
{ id: 11, name: "Second tag" },
];
expect(
books.answer("patch_books_by_id", { id: "2", body: { tags } }).body,
).toMatchObject({ id: 2, tags });
});
const TOKENS = ["Abcdef0123456789A", "Abcdef0123456789B", "Abcdef0123456789C"];
it("withholds a generated create token copied into a nested constant", () => {
const first = { ...FIRST_BOOK, version: TOKENS[0]! };
const second = { ...SECOND_BOOK, version: TOKENS[1]! };
const third = {
...book(3, "Created", bookClock(3)),
version: TOKENS[2]!,
metadata: { versions: [{ value: TOKENS[2]! }] },
};
const books = servedFrom([
["GET", "/api/books", [first, second]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["POST", "/api/books", third, 201, { name: "Created" }],
["GET", "/api/books/3", third],
]);
expect(books.state.writes.get("post_books")).toMatchObject({
served: false,
reason: "unexplained-change",
});
});
it("withholds an update token already present in a nested unchanged field", () => {
const first = {
...FIRST_BOOK,
version: TOKENS[0]!,
metadata: { versions: [TOKENS[1]!] },
};
const second = { ...SECOND_BOOK, version: TOKENS[2]!, metadata: {} };
const changed = { ...first, version: TOKENS[1]! };
const books = servedFrom([
["GET", "/api/books", [first, second]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["PATCH", "/api/books/1", changed, 200, {}],
["GET", "/api/books/1", changed],
["GET", "/api/books", [changed, second]],
]);
expect(books.state.writes.get("patch_books_by_id")).toMatchObject({
served: false,
});
});
it.each(["2", "02"])(
"withholds a bulk acknowledgement with string count %s",
(deleted) => {
const rows: TimedCaptureRow[] = [
[
"GET",
"/api/books",
[
{ ...FIRST_BOOK, id: 10 },
{ ...SECOND_BOOK, id: 20 },
],
],
["GET", "/api/books/10", { ...FIRST_BOOK, id: 10 }],
["GET", "/api/books/20", { ...SECOND_BOOK, id: 20 }],
["DELETE", "/api/books", { deleted }, 200, { ids: [10, 20] }],
["GET", "/api/books/10", BOOK_NOT_FOUND, 404],
["GET", "/api/books/20", BOOK_NOT_FOUND, 404],
["GET", "/api/books", []],
];
expect(servedFrom(rows).state.writes.get("delete_books")).toMatchObject({
served: false,
reason: "body-unusable",
});
},
);
const nestedClockRows = (edit: {
[key: string]: string;
}): TimedCaptureRow[] => {
const first = {
...FIRST_BOOK,
edit: {
at: FIRST_BOOK.created_at!,
seen_at: FIRST_BOOK.created_at!,
by: "alice",
},
};
const second = {
...SECOND_BOOK,
edit: {
at: SECOND_BOOK.created_at!,
seen_at: SECOND_BOOK.created_at!,
by: "alice",
},
};
const third = {
...book(3, "Third", bookClock(3)),
edit: { at: bookClock(3), seen_at: bookClock(3), by: "alice" },
};
const fourth = { ...book(4, "Fourth", bookClock(5)), edit };
return [
["GET", "/api/books", [first, second]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["POST", "/api/books", third, 201, { name: "Third" }],
["GET", "/api/books/3", third],
["POST", "/api/books", fourth, 201, { name: "Fourth" }],
["GET", "/api/books/4", fourth],
["GET", "/api/books", [first, second, third, fourth]],
];
};
it.each<{ name: string; edit: { [key: string]: string } }>([
{
name: "changed editor",
edit: { at: bookClock(5), seen_at: bookClock(5), by: "bob" },
},
{
name: "added member",
edit: {
at: bookClock(5),
seen_at: bookClock(5),
by: "alice",
role: "admin",
},
},
{ name: "omitted member", edit: { at: bookClock(5), seen_at: bookClock(5) } },
{
name: "different clock values",
edit: { at: bookClock(5), seen_at: bookClock(6), by: "alice" },
},
])(
"withholds creates whose nested-clock renderer cannot reproduce a later $name",
({ edit }) => {
expect(
servedFrom(nestedClockRows(edit)).state.writes.get("post_books"),
).toMatchObject({ served: false, reason: "unexplained-change" });
},
);
it("serves successive creates whose complete nested-clock objects are reproducible", () => {
const books = servedFrom(
nestedClockRows({ at: bookClock(5), seen_at: bookClock(5), by: "alice" }),
);
expect(books.state.writes.get("post_books")).toMatchObject({ served: true });
const result = books.answer("post_books", { body: { name: "Caller" } });
expect(result.body).toMatchObject({ name: "Caller", edit: { by: "alice" } });
const edit = (result.body as { edit: { at: string; seen_at: string } }).edit;
expect(edit.at).toBe(edit.seen_at);
});
it.each([0, 1])(
"checks the complete nested-clock update for a %s-second member difference",
(seconds) => {
const first = {
...FIRST_BOOK,
edit: { at: bookClock(-8), seen_at: bookClock(-8), by: "alice" },
};
const second = {
...SECOND_BOOK,
edit: { at: bookClock(-7), seen_at: bookClock(-7), by: "alice" },
};
const changed = {
...renamedBook(first, "Renamed", 3),
edit: { at: bookClock(3), seen_at: bookClock(3 + seconds), by: "alice" },
};
const books = servedFrom([
["GET", "/api/books", [first, second]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["PATCH", "/api/books/1", changed, 200, { name: "Renamed" }],
["GET", "/api/books/1", changed],
["GET", "/api/books", [changed, second]],
]);
expect(books.state.writes.get("patch_books_by_id")).toMatchObject({
served: seconds === 0,
});
if (seconds === 0) {
const result = books.answer("patch_books_by_id", {
id: "2",
body: { name: "Caller" },
});
expect(result.body).toMatchObject({ edit: { by: "alice" } });
const edit = (result.body as { edit: { at: string; seen_at: string } })
.edit;
expect(edit.at).toBe(edit.seen_at);
}
},
);
it.each([
{ bulk: false, unequal: false },
{ bulk: true, unequal: false },
{ bulk: false, unequal: true },
{ bulk: true, unequal: true },
])(
"validates the write response before its stored clock spelling (bulk=$bulk, unequal=$unequal)",
({ bulk, unequal }) => {
const edit = (seconds: number, fraction: string) => ({
at: new Date(bookClock(seconds)).toISOString().replace(".000Z", fraction),
seen_at: new Date(bookClock(seconds))
.toISOString()
.replace(".000Z", fraction),
by: "alice",
});
const first = { ...FIRST_BOOK, edit: edit(-8, ".000123Z") };
const second = { ...SECOND_BOOK, edit: edit(-7, ".000123Z") };
const changed = {
...renamedBook(first, "Renamed", 3),
edit: {
...edit(3, ".000Z"),
seen_at: new Date(bookClock(unequal ? 4 : 3)).toISOString(),
},
};
const read = { ...changed, edit: edit(3, ".000123Z") };
const books = servedFrom([
["GET", "/api/books", [first, second]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
[
"PATCH",
bulk ? "/api/books" : "/api/books/1",
bulk ? [changed] : changed,
200,
bulk ? [{ id: 1, name: "Renamed" }] : { name: "Renamed" },
],
["GET", "/api/books/1", read],
["GET", "/api/books", [read, second]],
]);
const name = bulk ? "patch_books" : "patch_books_by_id";
expect(books.state.writes.get(name)).toMatchObject({ served: !unequal });
if (!unequal) {
const result = books.answer(
name,
bulk
? { body: [{ id: 2, name: "Caller" }] }
: { id: "2", body: { name: "Caller" } },
);
const entity = (bulk ? (result.body as unknown[])[0] : result.body) as {
name: string;
edit: { at: string; seen_at: string; by: string };
};
expect(entity).toMatchObject({ name: "Caller", edit: { by: "alice" } });
expect(entity.edit.at).toBe(entity.edit.seen_at);
expect(entity.edit.at).toMatch(/\.\d{3}Z$/u);
}
},
);
it("keeps a create's nested reference pinned when an unobserved call varies its id", () => {
const card = (id: number, name: string, collection_id: number) => ({
id,
name,
collection_id,
collection: {
id: collection_id,
name: collection_id === 8 ? "Ledger" : "Other",
},
});
const first = card(1, "First", 8);
const second = card(2, "Second", 9);
const third = card(3, "Third", 8);
const fourth = card(4, "Fourth", 9);
const cards = servedFrom([
["GET", "/api/cards", [first, second]],
["GET", "/api/cards/1", first],
["GET", "/api/cards/2", second],
["POST", "/api/cards", third, 201, { name: "Third", collection_id: 8 }],
["GET", "/api/cards/3", third],
["POST", "/api/cards", fourth, 201, { name: "Fourth", collection_id: 9 }],
]);
const plan = cards.state.writes.get("post_cards");
expect(plan?.body?.fixed).toContainEqual({
member: "collection_id",
sent: 8,
});
expect(plan).toMatchObject({ served: false });
const catalog = servedCatalog(cards.source, cards.state, "cards");
if (!catalog.ok) throw catalog.error;
expect(catalog.value.tools.map(({ name }) => name)).not.toContain(
"post_cards",
);
});
it("refuses a create's unsupported dependent state even when an unobserved call varied it", () => {
const post = (id: number, status: string, published_at: string | null) => ({
id,
title: `Post ${String(id)}`,
status,
published_at,
});
const first = post(1, "published", "2026-10-01T09:00:00Z");
const second = post(2, "published", "2026-10-01T10:00:00Z");
const draft = post(3, "draft", null);
const posts = servedFrom([
["GET", "/api/posts", [first, second]],
["GET", "/api/posts/1", first],
["GET", "/api/posts/2", second],
["POST", "/api/posts", draft, 201, { title: "Post 3", status: "draft" }],
["GET", "/api/posts/3", draft],
["POST", "/api/posts", "", 204, { title: "Post 4", status: "published" }],
]);
expect(posts.state.writes.get("post_posts")).toMatchObject({ served: true });
const before = posts.snapshot();
expect(
posts.refusal("post_posts", {
body: { title: "Caller", status: "published" },
}),
).toMatchObject({ code: "invalid_request" });
expect(posts.snapshot()).toBe(before);
expect(
posts.answer("post_posts", {
body: { title: "Caller", status: "draft" },
}).body,
).toMatchObject({ title: "Caller", status: "draft", published_at: null });
});
it("drops a write's confirmation when its read view later disproves learned array sorting", () => {
const tagged = (book: typeof FIRST_BOOK, tags: string[]) => ({
...book,
tags,
});
const first = tagged(FIRST_BOOK, ["alpha", "beta"]);
const second = tagged(SECOND_BOOK, ["gamma", "omega"]);
const written = tagged(SECOND_BOOK, ["zeta", "delta"]);
const books = servedFrom([
["GET", "/api/books", [first, second]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["PATCH", "/api/books/2", written, 200, { tags: ["zeta", "delta"] }],
["GET", "/api/books/2", tagged(SECOND_BOOK, ["delta", "zeta"])],
["GET", "/api/books/2", written],
]);
expect(books.state.views.get("get_books_by_id")).toMatchObject({
shape: "recorded",
});
expect(books.state.writes.get("patch_books_by_id")).toMatchObject({
served: false,
checked_record_ordinals: [],
});
});
it("reloads an unchanged snapshot after a view with its own clock spellings disproves sorting", () => {
const first = { ...FIRST_BOOK, tags: ["alpha", "beta"] };
const second = { ...SECOND_BOOK, tags: ["gamma", "omega"] };
const written = { ...second, tags: ["zeta", "delta"] };
const rows: TimedCaptureRow[] = [
["GET", "/api/books", [first, second].map(offsetListed)],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["PATCH", "/api/books/2", written, 200, { tags: written.tags }],
[
"GET",
"/api/books",
[first, { ...written, tags: ["delta", "zeta"] }].map(offsetListed),
],
];
const consistent = servedFrom(rows);
expect(consistent.state.entities[0]?.view_fields).toContainEqual({
tool: "get_books",
fields: {
created_at: offsetListed(first).created_at,
updated_at: offsetListed(first).updated_at,
},
});
expect(consistent.store.replace(consistent.store.snapshot()).ok).toBe(true);
const books = servedFrom([
...rows,
["GET", "/api/books", [first, written].map(offsetListed)],
]);
expect(books.state.views.get("get_books")).toMatchObject({
shape: "recorded",
});
const snapshot = books.store.snapshot();
expect(books.store.replace(snapshot)).toMatchObject({ ok: true });
expect(books.store.snapshot()).toEqual(snapshot);
expect(books.answer("get_books_by_id", { id: "1" }).body).toEqual(first);
});
it.each([
{ wrong: false, mixed: false },
{ wrong: true, mixed: false },
{ wrong: false, mixed: true },
{ wrong: true, mixed: true },
])(
"keeps contradictions after retiring a sorted list while an item proof survives (wrong=$wrong, mixed=$mixed)",
({ wrong, mixed }) => {
const first = { ...FIRST_BOOK, tags: ["alpha", "beta"] };
const second = { ...SECOND_BOOK, tags: ["gamma", "omega"] };
const written = { ...second, tags: ["zeta", "delta"] };
const later = { ...first, tags: ["theta", "epsilon"] };
const books = servedFrom([
["GET", "/api/books", [first, second]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["PATCH", "/api/books/2", written, 200, { tags: written.tags }],
["GET", "/api/books/2", { ...written, tags: ["delta", "zeta"] }],
["PATCH", "/api/books/1", later, 200, { tags: later.tags }],
[
"GET",
"/api/books",
[
{ ...later, tags: wrong ? ["wrong", "values"] : later.tags },
mixed ? { ...written, tags: ["delta", "zeta"] } : written,
],
],
]);
expect(books.state.views.get("get_books")).toMatchObject({
shape: "recorded",
});
expect(books.state.writes.get("patch_books_by_id")).toMatchObject({
served: !wrong,
checked_record_ordinals: [4],
contradicted_by: wrong ? 6 : null,
});
},
);
it("drops state with no surviving view while preserving another type and snapshot reload", () => {
const first = { ...FIRST_BOOK, tags: ["alpha", "beta"] };
const second = { ...SECOND_BOOK, tags: ["gamma", "omega"] };
const written = { ...second, tags: ["zeta", "delta"] };
const authors = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
const books = servedFrom([
["GET", "/api/books", [first, second]],
["GET", "/api/authors", authors],
["PATCH", "/api/books/2", written, 200, { tags: written.tags }],
["GET", "/api/books", [first, { ...written, tags: ["delta", "zeta"] }]],
["GET", "/api/books", [first, written]],
]);
expect(books.state.views.get("get_books")).toMatchObject({
shape: "recorded",
});
expect(books.state.types.map((type) => type.collection_template)).toEqual([
"/api/authors",
]);
expect(books.state.entities.map((entity) => entity.type)).toEqual([
"/api/authors",
"/api/authors",
]);
expect(books.store.replace(books.store.snapshot()).ok).toBe(true);
expect(books.answer("get_authors").body).toEqual(authors);
});
it.each([false, true])(
"checks later writes through a retired sorted view without adding proof (wrong values: %s)",
(wrong) => {
const first = { ...FIRST_BOOK, tags: ["alpha", "beta"] };
const second = { ...SECOND_BOOK, tags: ["gamma", "omega"] };
const written = { ...second, tags: ["zeta", "delta"] };
const later = { ...first, tags: ["theta", "epsilon"] };
const books = servedFrom([
["GET", "/api/books", [first, second]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["PATCH", "/api/books/2", written, 200, { tags: written.tags }],
["GET", "/api/books/2", { ...written, tags: ["delta", "zeta"] }],
["GET", "/api/books", [first, written]],
["PATCH", "/api/books/1", later, 200, { tags: later.tags }],
[
"GET",
"/api/books",
[
{ ...later, tags: wrong ? ["wrong", "values"] : later.tags },
{ ...written, tags: ["delta", "zeta"] },
],
],
]);
expect(books.state.views.get("get_books")).toMatchObject({
shape: "recorded",
});
expect(books.state.writes.get("patch_books_by_id")).toMatchObject({
served: !wrong,
checked_record_ordinals: [4],
contradicted_by: wrong ? 7 : null,
});
},
);
it("withholds varied create states whose dependent fields still copy the first create", () => {
const first = { ...FIRST_BOOK, status: "draft", closed_at: null };
const second = {
...SECOND_BOOK,
status: "closed",
closed_at: SECOND_BOOK.created_at!,
};
const third = {
...book(3, "Third", "2026-10-01T10:00:00Z"),
status: "closed",
closed_at: SECOND_BOOK.created_at!,
};
const fourth = {
...book(4, "Fourth", bookClock(4)),
status: "draft",
closed_at: null,
};
const fifth = {
...book(5, "Fifth", bookClock(6)),
status: "closed",
closed_at: bookClock(6),
};
const books = servedFrom([
["GET", "/api/books", [first, second, third]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["GET", "/api/books/3", third],
["POST", "/api/books", fourth, 201, { name: "Fourth", status: "draft" }],
["GET", "/api/books/4", fourth],
["POST", "/api/books", fifth, 201, { name: "Fifth", status: "closed" }],
]);
expect(books.state.writes.get("post_books")).toMatchObject({ served: false });
});