From e967e453a5a3e5d04249aafad5d4454cd1d916aa Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 12 Oct 2026 08:49:15 +0200 Subject: [PATCH] thing-contract-ts: tests for the definitions' schema and catalog members; the private constructor is attacked with arguments that type-check Co-Authored-By: Claude Sonnet 5.5 --- .../thing-contract/test/definitions.test.ts | 77 +++++++++++++++++++ packages/thing-contract/test/types.ts | 5 +- 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 packages/thing-contract/test/definitions.test.ts diff --git a/packages/thing-contract/test/definitions.test.ts b/packages/thing-contract/test/definitions.test.ts new file mode 100644 index 0000000..2c47801 --- /dev/null +++ b/packages/thing-contract/test/definitions.test.ts @@ -0,0 +1,77 @@ +// The five definitions carry the crate's cultcache(type, schema) and, per document slot, the reference's member name +// and C# type name that a store's schema catalog is compared on. +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + assetCatalogDocument, + commandInvocationDocument, + commandResultDocument, + compositionDocument, + surfaceDocument, + thingContract, +} from "@gamecult/thing-contract"; + +const NS = "GameCult.Thing.Contract."; +const STRING = "System.String"; +const list = (item: string) => `System.Collections.Generic.List<${NS}${item}>`; + +const expected = [ + [surfaceDocument, "gamecult.thing.surface", [ + "0:ProviderId:" + STRING, + "1:Label:" + STRING, + "2:Version:System.Int64", + "3:Title:" + STRING, + `4:Root:${NS}Node`, + `5:Commands:${list("CommandDecl")}`, + ]], + [compositionDocument, "gamecult.thing.composition", [ + "0:Publisher:" + STRING, + "1:Name:" + STRING, + "2:Version:System.Int64", + `3:Params:${list("ParamDecl")}`, + `4:Body:${NS}Node`, + ]], + [commandInvocationDocument, "gamecult.thing.command_invocation", [ + "0:InvocationId:" + STRING, + "1:SurfaceKey:" + STRING, + "2:SurfaceVersion:System.Int64", + "3:Command:" + STRING, + `4:Payload:${NS}RecordPayload`, + `5:Captured:${list("CapturedInput")}`, + "6:Client:" + STRING, + "7:IssuedAt:" + STRING, + "8:Delegation:" + STRING, + ]], + [commandResultDocument, "gamecult.thing.command_result", [ + "0:InvocationId:" + STRING, + "1:Seq:System.Int32", + `2:Outcome:${NS}Outcome`, + ]], + [assetCatalogDocument, "gamecult.thing.asset_catalog", [ + "0:ProviderId:" + STRING, + "1:CatalogId:" + STRING, + "2:Version:System.Int64", + `3:Assets:${list("AssetEntry")}`, + ]], +] as const; + +for (const [definition, type, members] of expected) { + test(`${type}: type, schema and catalog members`, () => { + assert.equal(definition.type, type); + assert.equal(definition.schemaName, type); + assert.equal(definition.schemaVersion, `${type}.v1`); + assert.deepEqual( + definition.members?.map((member) => `${member.slot}:${member.memberName}:${member.typeName}`), + members, + ); + }); +} + +test("the registry lists the five definitions, in the crate's order", () => { + assert.deepEqual( + thingContract.definitions.map((definition) => definition.type), + expected.map(([, type]) => type), + ); + assert.equal(new Set(thingContract.definitions).size, 5); +}); diff --git a/packages/thing-contract/test/types.ts b/packages/thing-contract/test/types.ts index 5e7b963..307a586 100644 --- a/packages/thing-contract/test/types.ts +++ b/packages/thing-contract/test/types.ts @@ -17,9 +17,10 @@ export const shaped: CommandResult = plain; // @ts-expect-error export const spread: CommandResult = { ...result }; -// The constructor is private. +// The constructor is private: even a call whose arguments all type-check (the door argument is typed `never` here) is +// refused, so the error is the access, not the arity. // @ts-expect-error -export const constructed = new CommandResult("x", 1, { kind: "Failed", reason: "r" }); +export const constructed = new CommandResult(undefined as never, "x", 1, { kind: "Failed", reason: "r" }); // The fields are read-only. // @ts-expect-error