mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 05:16:16 +00:00
Fixes #6343. Normalize renewal identities once, preserve source spelling, and refuse aliases the shared generators cannot reproduce. Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
136 lines
4.8 KiB
TypeScript
136 lines
4.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { servedFrom } from "../../../tests/fixtures/environmentStateServing.js";
|
|
|
|
const uuid = (n: number): string =>
|
|
`00000000-0000-4000-8000-${String(n).padStart(12, "0")}`;
|
|
|
|
describe("assigned values preserve every recorded relation", () => {
|
|
it("freezes a copied member when another template would retain its old value", () => {
|
|
const item = (id: number, slug: string) => ({
|
|
id,
|
|
code: slug,
|
|
url: `/${slug}`,
|
|
mixed: `/${id}/${slug}`,
|
|
});
|
|
const first = item(1, "first-slug");
|
|
const second = item(2, "second-slug");
|
|
const third = item(3, "abcdefgh");
|
|
const books = servedFrom([
|
|
["GET", "/api/books", [first, second]],
|
|
["GET", "/api/books/1", first],
|
|
["GET", "/api/books/2", second],
|
|
["POST", "/api/books", third, 201, { customSlug: "abcdefgh" }],
|
|
["GET", "/api/books/3", third],
|
|
]);
|
|
expect(books.state.writes.get("post_books")).toMatchObject({
|
|
served: true,
|
|
body: { fixed: [{ member: "customSlug", sent: "abcdefgh" }] },
|
|
});
|
|
expect(
|
|
books.refusal("post_books", { body: { customSlug: "ijklmnop" } }),
|
|
).toMatchObject({ code: "invalid_request" });
|
|
expect(
|
|
books.answer("post_books", { body: { customSlug: "abcdefgh" } }).body,
|
|
).toMatchObject({ code: "abcdefgh", url: "/abcdefgh" });
|
|
});
|
|
|
|
it.each(["nested", "scalar"] as const)(
|
|
"withholds an update whose generated scalar shares a fresh UUID with a %s field",
|
|
(shape) => {
|
|
const linked = (id: number, value: string) => ({
|
|
id,
|
|
name: "Before",
|
|
version: value,
|
|
link: shape === "nested" ? [{ id: value }] : value,
|
|
});
|
|
const first = linked(1, uuid(1));
|
|
const second = linked(2, uuid(2));
|
|
const after = { ...linked(1, uuid(3)), name: "After" };
|
|
const books = servedFrom([
|
|
["GET", "/api/books", [first, second]],
|
|
["GET", "/api/books/1", first],
|
|
["GET", "/api/books/2", second],
|
|
["PATCH", "/api/books/1", after, 200, { name: "After" }],
|
|
["GET", "/api/books/1", after],
|
|
]);
|
|
expect(books.state.writes.get("patch_books_by_id")).toMatchObject({
|
|
served: false,
|
|
reason: "unexplained-change",
|
|
});
|
|
},
|
|
);
|
|
|
|
it.each(["sent", "held", "request-only"] as const)(
|
|
"withholds nested renewal linked to a %s UUID outside its generation owner",
|
|
(source) => {
|
|
const first = {
|
|
id: 1,
|
|
name: "Before",
|
|
ownerId: uuid(3),
|
|
rows: [{ id: uuid(1) }],
|
|
};
|
|
const second = {
|
|
id: 2,
|
|
name: "Second",
|
|
ownerId: uuid(9),
|
|
rows: [{ id: uuid(2) }],
|
|
};
|
|
const after = { ...first, name: "After", rows: [{ id: uuid(3) }] };
|
|
// Keep the request-only alias out of the output's non-renewed fields.
|
|
if (source === "request-only") first.ownerId = after.ownerId = uuid(8);
|
|
const body =
|
|
source === "sent"
|
|
? { name: "After", ownerId: uuid(3) }
|
|
: source === "request-only"
|
|
? { name: "After", extra: { reference: uuid(3) } }
|
|
: { name: "After" };
|
|
const books = servedFrom([
|
|
["GET", "/api/books", [first, second]],
|
|
["GET", "/api/books/1", first],
|
|
["GET", "/api/books/2", second],
|
|
["PATCH", "/api/books/1", after, 200, body],
|
|
["GET", "/api/books/1", after],
|
|
]);
|
|
expect(books.state.writes.get("patch_books_by_id")).toMatchObject({
|
|
served: false,
|
|
reason: "unexplained-change",
|
|
});
|
|
},
|
|
);
|
|
|
|
it("renews a mixed-case repeated nested identity once while preserving both spellings", () => {
|
|
const lettered = (letter: string) =>
|
|
`${letter.repeat(8)}-${letter.repeat(4)}-4${letter.repeat(3)}-8${letter.repeat(3)}-${letter.repeat(12)}`;
|
|
const item = (id: number, letter: string) => ({
|
|
id,
|
|
name: "Before",
|
|
rows: [{ id: lettered(letter) }, { id: lettered(letter).toUpperCase() }],
|
|
});
|
|
const first = item(1, "a"),
|
|
second = item(2, "c");
|
|
const after = { ...item(1, "b"), name: "After" };
|
|
const books = servedFrom([
|
|
["GET", "/api/books", [first, second]],
|
|
["GET", "/api/books/1", first],
|
|
["GET", "/api/books/2", second],
|
|
["PATCH", "/api/books/1", after, 200, { name: "After" }],
|
|
["GET", "/api/books/1", after],
|
|
]);
|
|
expect(books.state.writes.get("patch_books_by_id")).toMatchObject({
|
|
served: true,
|
|
});
|
|
const answer = books.answer("patch_books_by_id", {
|
|
id: "1",
|
|
body: { name: "Changed" },
|
|
}).body;
|
|
expect(answer).toMatchObject({
|
|
name: "Changed",
|
|
rows: [{ id: expect.any(String) }, { id: expect.any(String) }],
|
|
});
|
|
const rows = (answer as { rows: { id: string }[] }).rows;
|
|
expect(rows[0]!.id).not.toBe(lettered("a"));
|
|
expect(rows[0]!.id).toBe(rows[0]!.id.toLowerCase());
|
|
expect(rows[1]!.id).toBe(rows[0]!.id.toUpperCase());
|
|
});
|
|
});
|