From c7a62d5e1cc1b70653d0110b3db01e7c3554e9ba Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 31 Oct 2026 07:13:26 +0000 Subject: [PATCH] Validate untyped hash and CLI fixture values The packaged xxHash export has no resolved type declaration. Check the factory, engine, and bigint result at the benchmark boundary, and treat CLI fixture rejection values as unknown. Add regression cases for invalid engines and hash results while retaining the real-engine and initialisation-count assertions. --- .../test-support/p2p-automatic-host.test.ts | 2 +- ...wHarnessFileContentHashBenchmarkRuntime.ts | 29 +++++++++++++++++-- ...leContentHashBenchmarkRuntime.unit.spec.ts | 21 +++++++++++++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/apps/cli/test-support/p2p-automatic-host.test.ts b/src/apps/cli/test-support/p2p-automatic-host.test.ts index 229fcc57..f4e03930 100644 --- a/src/apps/cli/test-support/p2p-automatic-host.test.ts +++ b/src/apps/cli/test-support/p2p-automatic-host.test.ts @@ -82,7 +82,7 @@ export async function runP2PAutomaticHostFixture(options: CLIOptions, context: C } const commandOutcome = runCommand(options, context).then( (result) => ({ type: "result" as const, result }), - (error) => ({ type: "error" as const, error }) + (error: unknown) => ({ type: "error" as const, error }) ); const connectedWaitStarted = Date.now(); while (!service.transportLifecycle.isConnected) { diff --git a/src/features/ReviewHarness/reviewHarnessFileContentHashBenchmarkRuntime.ts b/src/features/ReviewHarness/reviewHarnessFileContentHashBenchmarkRuntime.ts index 8d6b8831..12a49dbc 100644 --- a/src/features/ReviewHarness/reviewHarnessFileContentHashBenchmarkRuntime.ts +++ b/src/features/ReviewHarness/reviewHarnessFileContentHashBenchmarkRuntime.ts @@ -5,16 +5,41 @@ import { type FileContentHashBenchmarkOperations, } from "./reviewHarnessFileContentHashBenchmark"; +type XxHash64Engine = { + h64Raw(content: Uint8Array): unknown; +}; + +function isXxHashFactory(value: unknown): value is () => unknown { + return typeof value === "function"; +} + +function isXxHash64Engine(value: unknown): value is XxHash64Engine { + return typeof value === "object" && value !== null && "h64Raw" in value && typeof value.h64Raw === "function"; +} + export async function createFileContentHashBenchmarkOperations( clock: Pick = performance ): Promise { + const factory: unknown = xxhashNew; + if (!isXxHashFactory(factory)) { + throw new TypeError("The xxHash64 factory must be callable."); + } const started = clock.now(); - const xxHash = await xxhashNew(); + const xxHash: unknown = await factory(); const xxHashInitialisationMs = clock.now() - started; + if (!isXxHash64Engine(xxHash)) { + throw new TypeError("The xxHash64 engine must provide h64Raw."); + } return { xxHashInitialisationMs, createInput: createFixedFileContentHashInput, sha256: (content) => computeFileContentHash(content), - xxHash64: async (content) => xxHash.h64Raw(new Uint8Array(await content.arrayBuffer())), + xxHash64: async (content) => { + const hash = xxHash.h64Raw(new Uint8Array(await content.arrayBuffer())); + if (typeof hash !== "bigint") { + throw new TypeError("The xxHash64 result must be a bigint."); + } + return hash; + }, }; } diff --git a/src/features/ReviewHarness/reviewHarnessFileContentHashBenchmarkRuntime.unit.spec.ts b/src/features/ReviewHarness/reviewHarnessFileContentHashBenchmarkRuntime.unit.spec.ts index a5d6db38..c8f60005 100644 --- a/src/features/ReviewHarness/reviewHarnessFileContentHashBenchmarkRuntime.unit.spec.ts +++ b/src/features/ReviewHarness/reviewHarnessFileContentHashBenchmarkRuntime.unit.spec.ts @@ -1,6 +1,11 @@ -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { xxhashNew } from "octagonal-wheels/hash/xxhash.js"; import { createFileContentHashBenchmarkOperations } from "./reviewHarnessFileContentHashBenchmarkRuntime"; +vi.mock("octagonal-wheels/hash/xxhash.js", { spy: true }); + +afterEach(() => vi.restoreAllMocks()); + describe("Review Harness file content hash benchmark adapter", () => { it("uses the packaged SHA-256 helper and xxHash64 engine with fixed Blob content", async () => { const operations = await createFileContentHashBenchmarkOperations(); @@ -11,5 +16,19 @@ describe("Review Harness file content hash benchmark adapter", () => { expect(typeof (await operations.xxHash64(content))).toBe("bigint"); expect(await operations.sha256(content)).toBe(await operations.sha256(content)); expect(await operations.xxHash64(content)).toBe(await operations.xxHash64(content)); + expect(xxhashNew).toHaveBeenCalledOnce(); + }); + + it("rejects an engine without an xxHash64 operation", async () => { + vi.mocked(xxhashNew).mockResolvedValueOnce({}); + + await expect(createFileContentHashBenchmarkOperations()).rejects.toBeInstanceOf(TypeError); + }); + + it("rejects a hash result which is not a bigint", async () => { + vi.mocked(xxhashNew).mockResolvedValueOnce({ h64Raw: () => 123 }); + const operations = await createFileContentHashBenchmarkOperations(); + + await expect(operations.xxHash64(new Blob(["fixed content"]))).rejects.toBeInstanceOf(TypeError); }); });