import { describe, expect, it } from "vitest"; import { MODULE_PACKAGE_FORMAT, MODULE_PACKAGE_LICENSE_FILENAME, MODULE_PACKAGE_METADATA_FILENAME, modulePackageArchiveFileName, modulePackageRuntimePackage, safeParseModulePackageLicenseExpression, safeParseModulePackageMetadata, safeParseModulePackageRuntimePackage, safeParseModulePackageTarget, } from "../src/module-package.js"; const digest = "a".repeat(64); function metadata(overrides: Record = {}): Record { return { packageFormat: 1, module: { id: "example", version: "0.1.0" }, license: "AGPL-3.0-or-later", target: "any", runtime: { node: ">=24.11.0 <25" }, provenance: { revision: "86ae907f59458508e3277666cdd214f5bd7c1078", packager: { name: "@ekklesia/module-pack", version: "0.1.2" }, }, incorporates: { "@ekklesia/contracts": "0.1.2", "@ekklesia/module-sdk": "0.1.2" }, transitional: { databaseRuntime: { "drizzle-orm": "0.45.2" } }, files: [ { path: "LICENSE", size: 9, sha256: digest }, { path: "THIRD_PARTY_NOTICES", size: 10, sha256: digest }, { path: "dist/server/index.js", size: 20, sha256: digest }, { path: "ekklesia.module.json", size: 30, sha256: digest }, { path: "package.json", size: 40, sha256: digest }, ], ...overrides, }; } describe("Ekklesia Module Package v1 metadata (ADR 0049)", () => { it("names the format, its metadata file, and the recommended archive file name", () => { expect(MODULE_PACKAGE_FORMAT).toBe(1); expect(MODULE_PACKAGE_LICENSE_FILENAME).toBe("LICENSE"); expect(MODULE_PACKAGE_METADATA_FILENAME).toBe("ekklesia.package.json"); expect(modulePackageArchiveFileName("example", "0.1.0", "any")).toBe( "example-0.1.0-any.tar.gz", ); }); it("accepts SPDX license expressions and rejects non-expressions without a policy allowlist", () => { for (const expression of [ "AGPL-3.0-or-later", "MIT OR Apache-2.0", "(MIT OR Apache-2.0) AND BSD-3-Clause", ]) { expect(safeParseModulePackageLicenseExpression(expression).success, expression).toBe(true); } for (const expression of ["", "UNLICENSED", "MIT OR", "not a license", {}]) { expect(safeParseModulePackageLicenseExpression(expression).success, String(expression)).toBe( false, ); } }); it("requires the package license declaration and canonical LICENSE payload entry", () => { const withoutLicense = metadata(); delete withoutLicense.license; expect(safeParseModulePackageMetadata(withoutLicense).success).toBe(false); const withoutLicenseFile = metadata(); withoutLicenseFile.files = (withoutLicenseFile.files as { path: string }[]).filter( (file) => file.path !== "LICENSE", ); expect(safeParseModulePackageMetadata(withoutLicenseFile).success).toBe(false); const emptyLicenseFile = metadata(); emptyLicenseFile.files = (emptyLicenseFile.files as { path: string; size: number }[]).map( (file) => (file.path === "LICENSE" ? { ...file, size: 0 } : file), ); expect(safeParseModulePackageMetadata(emptyLicenseFile).success).toBe(false); }); it("accepts a complete any-target package description", () => { expect(safeParseModulePackageMetadata(metadata()).success).toBe(true); }); it("accepts a package that incorporates nothing and needs no transitional facts", () => { const { transitional: _transitional, ...rest } = metadata(); expect(safeParseModulePackageMetadata({ ...rest, incorporates: {} }).success).toBe(true); }); it.each([ ["an unknown top-level field", { signature: "x" }], ["format 2", { packageFormat: 2 }], ["a missing source revision", { provenance: { packager: { name: "p", version: "1.0.0" } } }], [ "an abbreviated revision", { provenance: { revision: "86ae907", packager: { name: "p", version: "1.0.0" } } }, ], ["a non-semver module version", { module: { id: "example", version: "1.0" } }], ["an invalid module id", { module: { id: "Example", version: "0.1.0" } }], ["an invalid Node range", { runtime: { node: "soon" } }], ["native runtime facts on an any target", { runtime: { node: ">=24", nodeApi: 9 } }], ["a non-Ekklesia incorporated package", { incorporates: { zod: "4.4.3" } }], [ "an incorporated range instead of a version", { incorporates: { "@ekklesia/module-sdk": "^0.1.0" } }, ], ["an unknown transitional key", { transitional: { orm: {} } }], ["an unknown database runtime", { transitional: { databaseRuntime: { prisma: "6.0.0" } } }], ])("rejects %s", (_label, overrides) => { expect(safeParseModulePackageMetadata(metadata(overrides)).success).toBe(false); }); it.each([ ["an absolute path", "/etc/passwd"], ["a parent segment", "dist/../../secret"], ["a dot segment", "./package.json"], ["an empty segment", "dist//index.js"], ["a backslash", "dist\\index.js"], ["a NUL byte", "dist/\u0000"], ["the metadata file itself", "ekklesia.package.json"], ])("rejects a file entry with %s", (_label, path) => { const files = [{ path, size: 1, sha256: digest }]; expect(safeParseModulePackageMetadata(metadata({ files })).success).toBe(false); }); it("requires file entries in byte-wise path order, without duplicates", () => { const entry = (path: string) => ({ path, size: 1, sha256: digest }); expect( safeParseModulePackageMetadata(metadata({ files: [entry("b"), entry("a")] })).success, ).toBe(false); expect( safeParseModulePackageMetadata(metadata({ files: [entry("a"), entry("a")] })).success, ).toBe(false); // Byte-wise, not locale order: upper case sorts before lower case. expect( safeParseModulePackageMetadata( metadata({ files: [entry("LICENSE"), entry("THIRD"), entry("dist")] }), ).success, ).toBe(true); }); it.each([ ["an uppercase digest", { sha256: "A".repeat(64) }], ["a short digest", { sha256: "a".repeat(63) }], ["a negative size", { size: -1 }], ["a fractional size", { size: 1.5 }], ])("rejects a file entry with %s", (_label, override) => { const files = [{ path: "package.json", size: 1, sha256: digest, ...override }]; expect(safeParseModulePackageMetadata(metadata({ files })).success).toBe(false); }); }); describe("module package targets", () => { it.each([ "any", "linux-x64-glibc", "linux-arm64-glibc", "linux-x64-musl", "darwin-arm64", ])("accepts %s", (target) => { expect(safeParseModulePackageTarget(target).success).toBe(true); }); it.each([ "", "ANY", "linux-x64", "linux-x64-gnu", "darwin-arm64-glibc", "linux", "any-x64", ])("rejects %j", (target) => { expect(safeParseModulePackageTarget(target).success).toBe(false); }); }); describe("the generated runtime package.json", () => { it("holds exactly what module discovery reads", () => { expect( modulePackageRuntimePackage({ name: "@ekklesia/module-template", version: "0.1.0" }), ).toEqual({ name: "@ekklesia/module-template", version: "0.1.0", private: true, type: "module", ekklesia: { layer: "module" }, }); }); it("accepts the generated shape and nothing more", () => { const generated = modulePackageRuntimePackage({ name: "@scope/example", version: "1.2.3" }); expect(safeParseModulePackageRuntimePackage(generated).success).toBe(true); for (const extra of [ { dependencies: { "@ekklesia/module-sdk": "workspace:*" } }, { scripts: { build: "tsc" } }, { exports: { ".": "./dist/server/index.js" } }, { imports: { "#x": "./x.js" } }, ]) { expect(safeParseModulePackageRuntimePackage({ ...generated, ...extra }).success).toBe(false); } expect(safeParseModulePackageRuntimePackage({ ...generated, type: "commonjs" }).success).toBe( false, ); }); });