import {
afterEach,
describe,
expect,
it,
vi
} from "vitest";
import {
mkdtemp,
mkdir,
rm,
writeFile
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createHash } from "node:crypto";
import {
ApplicationRequestError,
createApplicationService
} from "../src/index.js";
import {
MAXIMUM_DOCX_MARKDOWN_LENGTH,
defaultExportConfig
} from "@md-to-pdf/core";
let temporaryDirectory: string | undefined;
afterEach(async () => {
if (temporaryDirectory) {
await rm(temporaryDirectory, {
recursive: true,
force: true
});
}
temporaryDirectory = undefined;
});
async function createThemeFixture() {
temporaryDirectory = await mkdtemp(
join(tmpdir(), "md-to-pdf-application-")
);
const bundledRoot = join(temporaryDirectory, "bundled");
const localRoot = join(temporaryDirectory, "local");
const themeRoot = join(bundledRoot, "test-theme");
const sharedFontRoot = join(
bundledRoot,
"_shared",
"official-fonts"
);
await mkdir(join(themeRoot, "fonts"), { recursive: true });
await mkdir(sharedFontRoot, { recursive: true });
await mkdir(localRoot, { recursive: true });
await writeFile(
join(themeRoot, "theme.json"),
JSON.stringify({
manifestVersion: 1,
id: "test-theme",
name: "测试主题",
version: "1.0.0",
description: "共享应用服务测试主题",
author: "test",
license: "MIT",
entry: "theme.css",
domPreset: "typora",
defaultFontSize: "16px",
supportedFeatures: ["code", "table"],
docxFonts: {
faces: [
{
family: "Test",
source: "fonts/test.woff2",
license: "OFL-1.1"
},
{
family: "SharedTest",
source:
"theme-shared:official-fonts/shared.woff2",
license: "GPL-3.0-with-font-exception"
}
]
},
pageDefaults: {
margins: {
top: "37mm",
right: "26mm",
bottom: "35mm",
left: "28mm"
}
},
bundled: true
}),
"utf8"
);
await writeFile(
join(themeRoot, "theme.css"),
[
"@font-face {",
" font-family: Test;",
" src: url('./fonts/test.woff2') format('woff2');",
"}",
"@font-face {",
" font-family: SharedTest;",
" src: url('theme-shared:official-fonts/shared.woff2') format('woff2');",
"}",
"#write { font-family: Test; }"
].join("\n"),
"utf8"
);
await writeFile(
join(themeRoot, "fonts", "test.woff2"),
Buffer.from([0, 1, 2, 3])
);
await writeFile(
join(sharedFontRoot, "shared.woff2"),
Buffer.from([4, 5, 6, 7])
);
await writeFile(
join(sharedFontRoot, "fonts.css"),
[
"@font-face {",
" font-family: SharedTest;",
" src: url('./shared.woff2') format('woff2');",
"}"
].join("\n"),
"utf8"
);
return { bundledRoot, localRoot };
}
function sha256(content: Uint8Array | string) {
return createHash("sha256").update(content).digest("hex");
}
async function createFontPackFixture(
options: {
corruptWebHash?: boolean;
minimumAppVersion?: string;
targets?: string[];
} = {}
) {
const root = join(temporaryDirectory!, "font-packs");
const directory = join(root, "test-font-pack", "1.0.0");
const web = Buffer.from([10, 11, 12, 13, 14]);
const docx = Buffer.from([20, 21, 22, 23, 24, 25]);
const license = "SIL Open Font License 1.1";
await mkdir(join(directory, "fonts"), { recursive: true });
await writeFile(join(directory, "fonts", "test.woff2"), web);
await writeFile(join(directory, "fonts", "test.ttf"), docx);
await writeFile(join(directory, "OFL.txt"), license, "utf8");
await writeFile(
join(directory, "font-pack.json"),
JSON.stringify({
manifestVersion: 1,
id: "test-font-pack",
version: "1.0.0",
name: "测试字体包",
description: "应用服务字体包测试",
license: "OFL-1.1",
licenseFile: "OFL.txt",
licenseSha256: sha256(license),
licenseBytes: Buffer.byteLength(license),
compatibility: {
minimumAppVersion: options.minimumAppVersion ?? "0.6.0",
maximumAppVersionExclusive: "0.7.0"
},
faces: [
{
id: "test-regular",
targets: options.targets ?? ["Test"],
weight: 400,
style: "normal",
web: {
path: "fonts/test.woff2",
format: "woff2",
bytes: web.byteLength,
sha256: options.corruptWebHash ? "0".repeat(64) : sha256(web)
},
docx: {
path: "fonts/test.ttf",
format: "truetype",
bytes: docx.byteLength,
sha256: sha256(docx)
}
}
]
}),
"utf8"
);
return { root, web, docx };
}
describe("共享应用服务", () => {
it("渲染安全 Markdown 文档", async () => {
const roots = await createThemeFixture();
const service = createApplicationService(roots);
const document = await service.render({
markdown: "# 文档\n\n",
language: "zh-CN"
});
expect(document.articleHtml).toContain('id="write"');
expect(document.articleHtml).not.toContain("