/*
RFC-1051: test fingerprint core modules — primitives, canonical-json, path-matcher, fingerprint, types, semantic.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1051: initial fingerprint core tests.
*/
import { test, expect } from "vitest";
import {
byteHash,
stableStringify,
stableJsonHash,
isSha256Digest,
} from "@warpgogol/werkstatt-shared/fingerprint";
import { normalizePathSep, shouldIgnore } from "../path-matcher.ts";
import {
snapshotCanonicalJsonObjectV1,
isCanonicalJsonObjectV1,
CANONICAL_JSON_V1,
} from "@warpgogol/werkstatt-shared/fingerprint";
import { fingerprintFile } from "../fingerprint.ts";
import type { FingerprintOptions, FingerprintFileResult, FingerprintResult } from "../types.ts";
import * as semantic from "../semantic.ts";
// --- primitives.ts ---
test("byteHash produces sha256 digest for string input", () => {
const hash = byteHash("test");
expect(isSha256Digest(hash)).toBe(true);
expect(hash).toMatch(/^sha256:[0-9a-f]{64}$/);
});
test("byteHash produces sha256 digest for Uint8Array input", () => {
const hash = byteHash(new Uint8Array([1, 2, 3]));
expect(isSha256Digest(hash)).toBe(true);
});
test("byteHash is deterministic", () => {
expect(byteHash("test")).toBe(byteHash("test"));
});
test("byteHash differs for different inputs", () => {
expect(byteHash("a")).not.toBe(byteHash("b"));
});
test("isSha256Digest validates correct format", () => {
expect(isSha256Digest("sha256:" + "a".repeat(64))).toBe(true);
expect(isSha256Digest("not-a-hash")).toBe(false);
expect(isSha256Digest("sha256:short")).toBe(false);
});
test("stableStringify produces sorted keys", () => {
const result = stableStringify({ b: 2, a: 1 });
expect(result).toBe('{"a":1,"b":2}');
});
test("stableJsonHash is deterministic for equivalent objects", () => {
expect(stableJsonHash({ b: 2, a: 1 })).toBe(stableJsonHash({ a: 1, b: 2 }));
});
// --- path-matcher.ts ---
test("normalizePathSep converts backslashes to forward slashes", () => {
expect(normalizePathSep("a\\b\\c")).toBe("a/b/c");
expect(normalizePathSep("a/b/c")).toBe("a/b/c");
});
test("shouldIgnore returns true when pattern is substring of path", () => {
expect(shouldIgnore("src/node_modules/x", ["node_modules"])).toBe(true);
expect(shouldIgnore("src/foo.ts", ["node_modules"])).toBe(false);
});
// --- canonical-json.ts ---
test("CANONICAL_JSON_V1 constant is defined", () => {
expect(CANONICAL_JSON_V1).toBe("werkstatt/canonical-json@1");
});
test("snapshotCanonicalJsonObjectV1 succeeds for plain object", () => {
const result = snapshotCanonicalJsonObjectV1({ a: 1, b: "test" });
expect(result.ok).toBe(true);
});
test("snapshotCanonicalJsonObjectV1 fails for non-object input", () => {
const result = snapshotCanonicalJsonObjectV1("not an object");
expect(result.ok).toBe(false);
});
test("isCanonicalJsonObjectV1 guards correctly", () => {
expect(isCanonicalJsonObjectV1(null)).toBe(false);
expect(isCanonicalJsonObjectV1("string")).toBe(false);
});
// --- types.ts (load verification) ---
test("fingerprint types module loads without error", () => {
const opts: FingerprintOptions = { mode: "byte" };
expect(opts.mode).toBe("byte");
const fileResult: FingerprintFileResult = {
path: "test",
mode: "byte",
normalizer: "text",
hash: "sha256:abc",
};
expect(fileResult.path).toBe("test");
const treeResult: FingerprintResult = {
algorithm: "sha256",
mode: "byte",
value: "sha256:abc",
files: [fileResult],
};
expect(treeResult.algorithm).toBe("sha256");
});
// --- fingerprint.ts ---
test("fingerprintFile produces hash for text content", async () => {
const { mkdtempSync, rmSync } = await import("node:fs");
const fs = await import("node:fs/promises");
const os = await import("node:os");
const path = await import("node:path");
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "fp-test-"));
try {
const filePath = path.join(tmpDir, "test.txt");
await fs.writeFile(filePath, "hello world\n");
const result = await fingerprintFile(filePath, { mode: "byte" });
expect(result.hash).toMatch(/^sha256:[0-9a-f]{64}$/);
expect(result.mode).toBe("byte");
} finally {
rmSync(tmpDir, { recursive: true, force: true });
}
});
// --- semantic.ts (load verification) ---
test("semantic module loads without error", () => {
expect(semantic).toBeDefined();
});