Thing/packages/thing-contract/test/keys.test.ts
Meta Vens b8ad36a189 thing-contract-ts: seq, version and segment checked on the encode door of the leaf (integer and bound) where a caller hands a number in
The factory and key builders took a non-integer through the decode leaf; the encode leaf refuses it. Deep chain bound corrected to 83 Box levels.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 08:40:20 +02:00

67 lines
3.6 KiB
TypeScript

// The key grammar of crates/thing-contract/src/keys.rs and the cases of tests/result.rs: keys are built from the fields,
// every local segment is a Segment, and two different field sets never build one key.
import assert from "node:assert/strict";
import test from "node:test";
import { assetCatalogKey, compositionKey, invocationKey, resultKey, surfaceKey } from "@gamecult/thing-contract";
import { CANARY } from "./builders.js";
const builders: [string, (segment: string) => string][] = [
["surfaceKey", (segment) => surfaceKey("p", segment)],
["compositionKey name", (segment) => compositionKey("pub", segment, 1)],
["assetCatalogKey", (segment) => assetCatalogKey("p", segment)],
];
for (const [name, build] of builders) {
test(`${name} refuses an empty, a dot and a dotted segment, naming Segment and not the value`, () => {
assert.throws(() => build(""), /Segment must be non-empty/);
assert.throws(() => build("."), /Segment must be free of '\.'/);
assert.throws(() => build("a.b"), /Segment must be free of '\.'/);
assert.throws(
() => build(`${CANARY}.x`),
(error: Error) => /Segment/.test(error.message) && !error.message.includes(CANARY),
);
});
}
test("the keys are spelled from the fields", () => {
assert.equal(invocationKey("an-invocation-no-constructor-can-guess"), "thing:invocation:an-invocation-no-constructor-can-guess");
assert.equal(resultKey("an-invocation-no-constructor-can-guess", 3), "thing:result:an-invocation-no-constructor-can-guess.3");
assert.equal(surfaceKey("p", "main"), "thing:surface:p.main");
assert.equal(compositionKey("pub", "card", 2), "thing:composition:pub.card.v2");
assert.equal(compositionKey("pub", "card", 2n ** 40n), "thing:composition:pub.card.v1099511627776");
assert.equal(assetCatalogKey("p", "icons"), "thing:asset_catalog:p.icons");
});
test("a result key refuses a seq below 1 and a composition key a version below 0", () => {
assert.throws(() => resultKey("x", 0), /Seq must be at least 1/);
assert.throws(() => compositionKey("pub", "card", -1), /Version must be at least 0/);
assert.equal(compositionKey("pub", "card", 0), "thing:composition:pub.card.v0");
assert.throws(() => compositionKey("pub", "card", 1.5), /int64/);
assert.throws(() => resultKey("x", 1.5), /int32/);
assert.throws(() => resultKey("x", 2 ** 31), /int32/);
});
test("keys are injective over their field sets", () => {
// A dotted provider with a plain label, and a plain provider with a dotted label: the second cannot be built.
assert.equal(surfaceKey("a.b", "c"), "thing:surface:a.b.c");
assert.throws(() => surfaceKey("a", "b.c"), /Segment/);
assert.notEqual(surfaceKey("a", "c"), surfaceKey("a.b", "c"));
assert.notEqual(surfaceKey("a.b", "c"), surfaceKey("a", "bc"));
// A composition splits at its last two dots: publisher, name, then the version.
assert.equal(compositionKey("a.b", "c", 1), "thing:composition:a.b.c.v1");
assert.notEqual(compositionKey("a.b", "c", 1), compositionKey("a", "bc", 1));
assert.notEqual(compositionKey("a", "c", 1), compositionKey("a", "c", 11));
assert.notEqual(compositionKey("a", "c", 1), compositionKey("a", "c", 0));
// A catalog likewise, under the asset_catalog prefix.
assert.equal(assetCatalogKey("a.b", "c"), "thing:asset_catalog:a.b.c");
assert.notEqual(assetCatalogKey("a.b", "c"), assetCatalogKey("a", "bc"));
// A result splits at its last dot, and the seq is digits.
assert.equal(resultKey("x.1", 2), "thing:result:x.1.2");
assert.equal(resultKey("x", 12), "thing:result:x.12");
assert.notEqual(resultKey("x.1", 2), resultKey("x", 12));
});