diff --git a/guardrails.suppressions.json b/guardrails.suppressions.json index 68e4f96..7d466c5 100644 --- a/guardrails.suppressions.json +++ b/guardrails.suppressions.json @@ -6,7 +6,7 @@ "typescript/no-unused-vars": 1, "unicorn/no-thenable": 1 }, - "tsExpectError": 2, + "tsExpectError": 0, "tsIgnore": 0, "safety": 5, "config": {}, diff --git a/knip.config.ts b/knip.config.ts index 3e248e9..6c1fc5e 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -3,9 +3,15 @@ import { antelopeKnipConfig } from "@antelopejs/tooling-configs/knip"; /** * `ajs module test` runs these compiled suites and builds its test project from * `src/antelope.test.ts`. The whole compiled tree ships in the package, and the - * modules implementing this interface run it as their conformance suite. + * modules implementing this interface run it as their conformance suite. The + * `*.untyped.spec.js` suites feed values the types forbid, so they stay + * untyped JavaScript. */ -const conformanceSuites = ["src/tests/**/*.test.ts", "src/antelope.test.ts"]; +const conformanceSuites = [ + "src/tests/**/*.test.ts", + "src/tests/**/*.untyped.spec.js", + "src/antelope.test.ts", +]; /** * No import Knip can follow reaches these files: `datasets/index.ts` is the diff --git a/src/tests/atomic_mutation.test.ts b/src/tests/atomic_mutation.test.ts index bead285..5f22b67 100644 --- a/src/tests/atomic_mutation.test.ts +++ b/src/tests/atomic_mutation.test.ts @@ -99,20 +99,6 @@ function ValidateRevision() { expectedRevision: { kind: "missing" }, }), ).not.to.throw(); - for (const expectedRevision of [ - null, - undefined, - "", - { kind: "missing", extra: true }, - ]) { - expect(() => - ValidateAtomicMutation("record-a", { - ...request, - // @ts-expect-error expectedRevision is null, undefined, "" or a tag with an extra key, off-contract values that ValidateAtomicMutation must refuse with a TypeError - expectedRevision, - }), - ).to.throw(TypeError); - } expect(() => table.atomicMutation("record-a", { ...request, @@ -198,16 +184,6 @@ function ValidateEquality() { }), ).not.to.throw(); } - for (const expectedValue of [null, undefined, {}, [], NaN, new Date(NaN)]) { - expect(() => - ValidateAtomicMutation("record-a", { - type: "deleteIfEqual", - field: "title", - // @ts-expect-error expectedValue is null, undefined, {}, [], NaN or an invalid Date, off-contract values that ValidateAtomicMutation must refuse with a TypeError - expectedValue, - }), - ).to.throw(TypeError); - } } type StubbedResult = diff --git a/src/tests/atomic_mutation.untyped.spec.js b/src/tests/atomic_mutation.untyped.spec.js new file mode 100644 index 0000000..b98e53f --- /dev/null +++ b/src/tests/atomic_mutation.untyped.spec.js @@ -0,0 +1,38 @@ +const { expect } = require("chai"); + +const { ValidateAtomicMutation } = require("../index"); + +const update = { + type: "update", + revisionField: "revision", + expectedRevision: "observed", + nextRevision: "fresh", + patch: { details: { count: 3 } }, +}; + +describe("Atomic mutation runtime refusal of off-contract values", () => { + it("refuses a null, undefined, empty or extended expected revision", () => { + for (const expectedRevision of [ + null, + undefined, + "", + { kind: "missing", extra: true }, + ]) { + expect(() => + ValidateAtomicMutation("record-a", { ...update, expectedRevision }), + ).to.throw(TypeError); + } + }); + + it("refuses a non-scalar or invalid expected value", () => { + for (const expectedValue of [null, undefined, {}, [], NaN, new Date(NaN)]) { + expect(() => + ValidateAtomicMutation("record-a", { + type: "deleteIfEqual", + field: "title", + expectedValue, + }), + ).to.throw(TypeError); + } + }); +}); diff --git a/tsconfig.json b/tsconfig.json index b9a277d..33a7e92 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "types": ["node", "mocha"], "esModuleInterop": true, "strict": true, + "allowJs": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "declaration": true,