/*
Unit tests for search covering buildSearchManifest; computeSearchManifestContentHash; constants. It also guards the documented public contract and regression-sensitive edge cases.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1311: Compass header sweep — declares the search suite's contract for test-file scaffold coverage.
*/
import { describe, it, expect } from "vitest";
import {
buildSearchManifest,
computeSearchManifestContentHash,
SEARCH_EMBEDDING_MODEL,
SEARCH_EMBEDDING_DIMENSIONS,
SEARCH_MANIFEST_SCHEMA_VERSION,
SEARCH_CHUNK_TEXT_MAX_LENGTH,
isNonKnowledgeFile,
NON_KNOWLEDGE_FILES,
type SearchChunk,
} from "./search.ts";
const baseChunk: SearchChunk = {
id: "de:/about:block-intro",
url: "/about",
lang: "de",
type: "page",
blockId: "block-intro",
heading: "About",
text: "About the company",
title: "About",
canonicalUrl: "https://example.example/about",
updatedAt: "2026-08-20",
};
describe("buildSearchManifest", () => {
it("produces a manifest with correct schema version and model", () => {
const manifest = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [baseChunk],
});
expect(manifest.schema).toBe(SEARCH_MANIFEST_SCHEMA_VERSION);
expect(manifest.model).toBe(SEARCH_EMBEDDING_MODEL);
expect(manifest.dimensions).toBe(SEARCH_EMBEDDING_DIMENSIONS);
});
it("sorts chunks by id for deterministic output", () => {
const chunkB: SearchChunk = { ...baseChunk, id: "de:/about:block-zzz" };
const chunkA: SearchChunk = { ...baseChunk, id: "de:/about:block-aaa" };
const manifest = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [chunkB, chunkA],
});
expect(manifest.chunks[0].id).toBe("de:/about:block-aaa");
expect(manifest.chunks[1].id).toBe("de:/about:block-zzz");
});
it("is deterministic — same input produces same contentHash regardless of chunk order", () => {
const chunkA: SearchChunk = { ...baseChunk, id: "aaa" };
const chunkB: SearchChunk = { ...baseChunk, id: "bbb" };
const m1 = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [chunkA, chunkB],
});
const m2 = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-28T00:00:00Z",
chunks: [chunkB, chunkA],
});
expect(m1.contentHash).toBe(m2.contentHash);
});
it("excludes generatedAt from contentHash", () => {
const m1 = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [baseChunk],
});
const m2 = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-28T00:00:00Z",
chunks: [baseChunk],
});
expect(m1.contentHash).toBe(m2.contentHash);
expect(m1.generatedAt).not.toBe(m2.generatedAt);
});
it("produces different contentHash when chunks change", () => {
const m1 = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [baseChunk],
});
const m2 = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [{ ...baseChunk, text: "Different text" }],
});
expect(m1.contentHash).not.toBe(m2.contentHash);
});
it("produces different contentHash when site changes", () => {
const m1 = buildSearchManifest({
site: "site-a",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [baseChunk],
});
const m2 = buildSearchManifest({
site: "site-b",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [baseChunk],
});
expect(m1.contentHash).not.toBe(m2.contentHash);
});
});
describe("computeSearchManifestContentHash", () => {
it("matches the hash produced by buildSearchManifest", () => {
const manifest = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [baseChunk],
});
const recomputed = computeSearchManifestContentHash(manifest);
expect(recomputed).toBe(manifest.contentHash);
});
it("excludes proof from contentHash", () => {
const manifest = buildSearchManifest({
site: "test-site",
model: SEARCH_EMBEDDING_MODEL,
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
generatedAt: "2026-08-27T00:00:00Z",
chunks: [baseChunk],
});
const withProof = {
...manifest,
proof: {
type: "Ed25519Signature2020",
created: "2026-08-27T00:00:00Z",
verificationMethod: "did:web:example.com#key-v1",
proofPurpose: "assertionMethod",
proofValue: "zFakeProofValue",
},
};
expect(computeSearchManifestContentHash(withProof)).toBe(manifest.contentHash);
});
});
describe("constants", () => {
it("SEARCH_CHUNK_TEXT_MAX_LENGTH is 2000", () => {
expect(SEARCH_CHUNK_TEXT_MAX_LENGTH).toBe(2000);
});
});
describe("isNonKnowledgeFile", () => {
it("returns true for search-manifest.json", () => {
expect(isNonKnowledgeFile("search-manifest.json")).toBe(true);
});
it("returns false for knowledge domain files", () => {
expect(isNonKnowledgeFile("company.json")).toBe(false);
expect(isNonKnowledgeFile("offer.json")).toBe(false);
expect(isNonKnowledgeFile("faq.json")).toBe(false);
});
it("returns false for arbitrary filenames", () => {
expect(isNonKnowledgeFile("foo.json")).toBe(false);
expect(isNonKnowledgeFile("readme.md")).toBe(false);
});
it("NON_KNOWLEDGE_FILES contains search-manifest.json", () => {
expect(NON_KNOWLEDGE_FILES).toContain("search-manifest.json");
});
});