Skip to content

Commit 23cd864

Browse files
committed
Add vitest tests and CI improvements for Node SDKs
Introduces vitest-based test suites for proofkit and verifier Node packages, including tests for canonical note building, JCS encoding, publishing, and verification logic. Updates package.json scripts to include 'test' for both packages. Simplifies CI workflow to always run tests if present. Minor Python test imports updated for type checking.
1 parent 9231cdd commit 23cd864

File tree

9 files changed

+612
-21
lines changed

9 files changed

+612
-21
lines changed

.github/workflows/ci-sdks.yml

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,25 @@ jobs:
4141
strategy:
4242
matrix:
4343
node-version: ["18", "20"]
44-
44+
4545
steps:
46-
- name: Checkout code
46+
- name: Checkout repo
4747
uses: actions/checkout@v4
48-
48+
4949
- name: Set up Node.js
5050
uses: actions/setup-node@v4
5151
with:
5252
node-version: ${{ matrix.node-version }}
53-
53+
5454
- name: Test @axiomatic_oracle/proofkit
5555
run: |
5656
cd packages_node/proofkit
5757
npm install
58-
echo "Checking for test script in @axiomatic_oracle/proofkit..."
59-
if npm run | grep -q " test"; then
60-
npm test
61-
else
62-
echo "No 'test' script found for @axiomatic_oracle/proofkit, skipping tests for now."
63-
fi
64-
58+
npm test
59+
6560
- name: Test @axiomatic_oracle/verifier
6661
run: |
6762
cd packages_node/verifier
6863
npm install
69-
echo "Checking for test script in @axiomatic_oracle/verifier..."
70-
if npm run | grep -q " test"; then
71-
npm test
72-
else
73-
echo "No 'test' script found for @axiomatic_oracle/verifier, skipping tests for now."
74-
fi
64+
npm test
65+

packages_node/proofkit/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"files": ["dist"],
1515
"sideEffects": false,
1616
"scripts": {
17-
"build": "tsc -p tsconfig.json"
17+
"build": "tsc -p tsconfig.json",
18+
"test": "vitest run"
1819
},
1920
"engines": {
2021
"node": ">=18"
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { describe, it, expect } from "vitest";
2+
import { Buffer } from "node:buffer";
3+
import {
4+
NOTE_MAX_BYTES,
5+
buildP1,
6+
canonicalNoteBytesP1,
7+
assertNoteSizeOK,
8+
buildCanonicalInput,
9+
computeInputHash,
10+
} from "../src/index.js";
11+
import { jcsSha256 } from "../src/jcs.js"
12+
13+
describe("buildP1 / canonical note / input hashing (proofkit)", () => {
14+
it("builds a valid p1 object with expected fields", () => {
15+
const p1 = buildP1({
16+
assetTag: "re:EUR",
17+
modelVersion: "model-v1",
18+
modelHashHex: "0".repeat(64),
19+
inputHashHex: "1".repeat(64),
20+
valueEUR: 123_456.78,
21+
uncertaintyLowEUR: 120_000,
22+
uncertaintyHighEUR: 130_000,
23+
timestampEpochSec: 1_700_000_000,
24+
});
25+
26+
expect(p1.s).toBe("p1");
27+
expect(p1.a).toBe("re:EUR");
28+
expect(p1.mv).toBe("model-v1");
29+
expect(p1.mh).toBe("0".repeat(64));
30+
expect(p1.ih).toBe("1".repeat(64));
31+
expect(typeof p1.v).toBe("number");
32+
expect(p1.u).toEqual([120_000, 130_000]);
33+
expect(p1.ts).toBe(1_700_000_000);
34+
});
35+
36+
it("rejects invalid hex hashes and intervals", () => {
37+
// bad model hash
38+
expect(() =>
39+
buildP1({
40+
assetTag: "re:EUR",
41+
modelVersion: "model-v1",
42+
modelHashHex: "xyz",
43+
inputHashHex: "1".repeat(64),
44+
valueEUR: 1,
45+
uncertaintyLowEUR: 0,
46+
uncertaintyHighEUR: 2,
47+
timestampEpochSec: 1_700_000_000,
48+
}),
49+
).toThrow(/modelHashHex/i);
50+
51+
// bad input hash
52+
expect(() =>
53+
buildP1({
54+
assetTag: "re:EUR",
55+
modelVersion: "model-v1",
56+
modelHashHex: "0".repeat(64),
57+
inputHashHex: "!",
58+
valueEUR: 1,
59+
uncertaintyLowEUR: 0,
60+
uncertaintyHighEUR: 2,
61+
timestampEpochSec: 1_700_000_000,
62+
}),
63+
).toThrow(/inputHashHex/i);
64+
65+
// invalid interval
66+
expect(() =>
67+
buildP1({
68+
assetTag: "re:EUR",
69+
modelVersion: "model-v1",
70+
modelHashHex: "0".repeat(64),
71+
inputHashHex: "1".repeat(64),
72+
valueEUR: 1,
73+
uncertaintyLowEUR: 200,
74+
uncertaintyHighEUR: 100,
75+
timestampEpochSec: 1_700_000_000,
76+
}),
77+
).toThrow(/uncertainty/i);
78+
});
79+
80+
it("produces stable canonical note bytes and hash", async () => {
81+
const base = {
82+
assetTag: "re:EUR",
83+
modelVersion: "model-v1",
84+
modelHashHex: "0".repeat(64),
85+
inputHashHex: "1".repeat(64),
86+
valueEUR: 123_456.78,
87+
uncertaintyLowEUR: 120_000,
88+
uncertaintyHighEUR: 130_000,
89+
timestampEpochSec: 1_700_000_000,
90+
};
91+
92+
const p1a = buildP1(base);
93+
const p1b = buildP1({ ...base });
94+
95+
const noteA = await canonicalNoteBytesP1(p1a);
96+
const noteB = await canonicalNoteBytesP1(p1b);
97+
98+
expect(noteA.size).toBe(noteA.bytes.length);
99+
expect(noteB.size).toBe(noteB.bytes.length);
100+
101+
expect(Buffer.from(noteA.bytes).toString("utf8")).toBe(
102+
Buffer.from(noteB.bytes).toString("utf8"),
103+
);
104+
expect(noteA.sha256).toBe(noteB.sha256);
105+
});
106+
107+
it("enforces NOTE_MAX_BYTES via assertNoteSizeOK", async () => {
108+
const p1 = buildP1({
109+
assetTag: "re:EUR",
110+
modelVersion: "model-v1",
111+
modelHashHex: "0".repeat(64),
112+
inputHashHex: "1".repeat(64),
113+
valueEUR: 1,
114+
uncertaintyLowEUR: 0,
115+
uncertaintyHighEUR: 2,
116+
timestampEpochSec: 1_700_000_000,
117+
});
118+
119+
// Should not throw with default NOTE_MAX_BYTES
120+
await expect(assertNoteSizeOK(p1)).resolves.toBeUndefined();
121+
expect(NOTE_MAX_BYTES).toBeGreaterThan(0);
122+
123+
// Force a failure with an artificially tiny limit
124+
await expect(assertNoteSizeOK(p1, 1)).rejects.toThrow(/too large/i);
125+
});
126+
127+
it("buildCanonicalInput filters and strips fields as expected", async () => {
128+
const rec = {
129+
keep: 1,
130+
drop: 2,
131+
skipNull: null,
132+
skipUndef: undefined as unknown,
133+
};
134+
135+
const allowed = ["keep", "skipNull", "skipUndef"];
136+
const cin = buildCanonicalInput(rec, allowed);
137+
138+
expect(cin).toEqual({ keep: 1 });
139+
140+
const hash1 = await computeInputHash(rec, allowed);
141+
const hash2 = await jcsSha256(cin);
142+
143+
expect(hash1).toBe(hash2);
144+
});
145+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { describe, it, expect } from "vitest";
2+
import { toJcsBytes, jcsSha256 } from "../src/jcs.js";
3+
4+
const td = new TextDecoder();
5+
6+
/**
7+
* Helper to decode JCS bytes into a UTF-8 string.
8+
*/
9+
function jcsString(obj: unknown): string {
10+
return td.decode(toJcsBytes(obj));
11+
}
12+
13+
describe("JCS / canonical JSON (proofkit)", () => {
14+
it("produces identical canonical JSON for equivalent objects", async () => {
15+
const obj1 = {
16+
s: "p1",
17+
a: "re:EUR",
18+
mv: "model-v1",
19+
mh: "0".repeat(64),
20+
ih: "1".repeat(64),
21+
v: 100.0,
22+
u: [90.0, 110.0],
23+
ts: 1_700_000_000,
24+
nested: {
25+
z: 3,
26+
a: 1,
27+
},
28+
};
29+
30+
// Same logical content, different key ordering
31+
const obj2 = {
32+
nested: {
33+
a: 1,
34+
z: 3,
35+
},
36+
u: [90.0, 110.0],
37+
v: 100.0,
38+
ih: "1".repeat(64),
39+
mh: "0".repeat(64),
40+
mv: "model-v1",
41+
a: "re:EUR",
42+
s: "p1",
43+
ts: 1_700_000_000,
44+
};
45+
46+
const s1 = jcsString(obj1);
47+
const s2 = jcsString(obj2);
48+
49+
expect(s1).toBe(s2);
50+
51+
const h1 = await jcsSha256(obj1);
52+
const h2 = await jcsSha256(obj2);
53+
54+
expect(h1).toBe(h2);
55+
});
56+
57+
it("normalizes undefined to null in canonical JSON", () => {
58+
const obj = { a: undefined as unknown, b: 1 };
59+
const s = jcsString(obj);
60+
61+
// JCS-style: undefined → null
62+
expect(s).toBe('{"a":null,"b":1}');
63+
});
64+
65+
it("rejects non-finite numbers", () => {
66+
const badValues = [NaN, Infinity, -Infinity];
67+
68+
for (const x of badValues) {
69+
const obj = { v: x };
70+
expect(() => toJcsBytes(obj)).toThrow(/Non-finite float/);
71+
}
72+
});
73+
74+
it("encodes bigint values as decimal strings", () => {
75+
const obj = { big: 42n };
76+
const s = jcsString(obj);
77+
78+
// Key ordering is deterministic: {"big":"42"}
79+
expect(s).toBe('{"big":"42"}');
80+
});
81+
});

0 commit comments

Comments
 (0)