158 lines
4.8 KiB
TypeScript
158 lines
4.8 KiB
TypeScript
/**
|
|
* DTP-02 / DTP-09 / DTP-26 — schema-version pinning + per-file
|
|
* consistency invariants + structured error context.
|
|
*/
|
|
|
|
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import type {
|
|
DriveTemplate,
|
|
FileTemplate,
|
|
FolderTemplate,
|
|
} from "../src/index.js";
|
|
import {
|
|
assertFile,
|
|
assertFolderFileInvariants,
|
|
type DriveTemplateError,
|
|
DTP_CODES,
|
|
TemplateNonceLru,
|
|
validateTemplate,
|
|
} from "../src/index.js";
|
|
import { DEFAULT_POLICY, makeKeys, signed } from "./_helpers.js";
|
|
|
|
const baseFile: FileTemplate = {
|
|
name: "x.bin",
|
|
fileLocId: "loc-1",
|
|
fileId: "f".repeat(64),
|
|
size: 100,
|
|
totalChunks: 1,
|
|
chunkSize: 1024,
|
|
checksum: "blake3:abcd",
|
|
flags: DEFAULT_POLICY,
|
|
manifestKey: "manifest:f",
|
|
manifestSig: new Uint8Array(64).fill(1),
|
|
};
|
|
|
|
describe("DTP-02 — schema version pinned to 1", () => {
|
|
it("schemaVersion ≠ 1 rejected with TEMPLATE_VERSION_UNSUPPORTED", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const keys = makeKeys();
|
|
const tpl = signed({ issuer: keys.publicHex }, keys);
|
|
// Cast through unknown to bypass the literal-1 type guard.
|
|
const broken = { ...tpl, schemaVersion: 2 } as unknown as DriveTemplate;
|
|
try {
|
|
validateTemplate(broken, {
|
|
clock: () => Date.now(),
|
|
nonceLru: new TemplateNonceLru(),
|
|
});
|
|
throw new Error("expected VERSION_UNSUPPORTED");
|
|
} catch (err) {
|
|
expect((err as DriveTemplateError).code).toBe(
|
|
DTP_CODES.TEMPLATE_VERSION_UNSUPPORTED
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("DTP-09 — per-file consistency invariants", () => {
|
|
it("chunkSize 164 rejected", { timeout: 5000 }, () => {
|
|
try {
|
|
assertFile({ ...baseFile, chunkSize: 164 }, "/x.bin");
|
|
throw new Error("expected MALFORMED");
|
|
} catch (err) {
|
|
expect((err as DriveTemplateError).code).toBe(
|
|
DTP_CODES.TEMPLATE_MALFORMED
|
|
);
|
|
expect((err as DriveTemplateError).context).toMatchObject({
|
|
file: "/x.bin",
|
|
});
|
|
}
|
|
});
|
|
|
|
it("totalChunks insufficient for size rejected", { timeout: 5000 }, () => {
|
|
try {
|
|
assertFile(
|
|
{ ...baseFile, totalChunks: 1, chunkSize: 1024, size: 4096 },
|
|
"/x.bin"
|
|
);
|
|
throw new Error("expected MALFORMED");
|
|
} catch (err) {
|
|
expect((err as DriveTemplateError).code).toBe(
|
|
DTP_CODES.TEMPLATE_MALFORMED
|
|
);
|
|
}
|
|
});
|
|
|
|
it("valid file passes through assertFile (positive path)", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
expect(() => assertFile(baseFile, "/x.bin")).not.toThrow();
|
|
});
|
|
|
|
it("empty manifestKey rejected", { timeout: 5000 }, () => {
|
|
try {
|
|
assertFile({ ...baseFile, manifestKey: "" }, "/x.bin");
|
|
throw new Error("expected MALFORMED");
|
|
} catch (err) {
|
|
expect((err as DriveTemplateError).code).toBe(
|
|
DTP_CODES.TEMPLATE_MALFORMED
|
|
);
|
|
}
|
|
});
|
|
|
|
it("malformed checksum rejected", { timeout: 5000 }, () => {
|
|
try {
|
|
assertFile({ ...baseFile, checksum: "md5:abc" }, "/x.bin");
|
|
throw new Error("expected MALFORMED");
|
|
} catch (err) {
|
|
expect((err as DriveTemplateError).code).toBe(
|
|
DTP_CODES.TEMPLATE_MALFORMED
|
|
);
|
|
}
|
|
});
|
|
|
|
it("empty manifestSig rejected", { timeout: 5000 }, () => {
|
|
try {
|
|
assertFile(
|
|
{ ...baseFile, manifestSig: new Uint8Array(0) },
|
|
"/x.bin"
|
|
);
|
|
throw new Error("expected MALFORMED");
|
|
} catch (err) {
|
|
expect((err as DriveTemplateError).code).toBe(
|
|
DTP_CODES.TEMPLATE_MALFORMED
|
|
);
|
|
}
|
|
});
|
|
|
|
it("nested folder: deepest file's failure surfaces with full path", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const tree: FolderTemplate = {
|
|
name: "/",
|
|
children: [
|
|
{
|
|
name: "photos",
|
|
children: [
|
|
{
|
|
name: "wedding",
|
|
children: [{ ...baseFile, chunkSize: 164 }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
try {
|
|
assertFolderFileInvariants(tree);
|
|
throw new Error("expected MALFORMED");
|
|
} catch (err) {
|
|
expect((err as DriveTemplateError).code).toBe(
|
|
DTP_CODES.TEMPLATE_MALFORMED
|
|
);
|
|
const ctx = (err as DriveTemplateError).context as { file: string };
|
|
expect(ctx.file).toContain("photos");
|
|
expect(ctx.file).toContain("wedding");
|
|
}
|
|
});
|
|
});
|