test: 建立 DOCX 自动化验收门禁
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
DOCX_MIME_TYPE,
|
||||
defaultExportConfig
|
||||
} from "@md-to-pdf/core";
|
||||
import { inspectDocxAcceptance } from "@md-to-pdf/docx-engine";
|
||||
import { buildApp } from "../dist/app.js";
|
||||
|
||||
const directory = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repositoryDirectory = path.resolve(directory, "../../..");
|
||||
const outputDirectory = path.join(
|
||||
repositoryDirectory,
|
||||
"output",
|
||||
"docx-acceptance"
|
||||
);
|
||||
const temporaryPrefix = "md-to-pdf-docx-";
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function temporaryDirectories() {
|
||||
return new Set(
|
||||
fs
|
||||
.readdirSync(os.tmpdir(), { withFileTypes: true })
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.isDirectory() && entry.name.startsWith(temporaryPrefix)
|
||||
)
|
||||
.map((entry) => entry.name)
|
||||
);
|
||||
}
|
||||
|
||||
const markdown = `---
|
||||
title: Server HTTP DOCX 验收
|
||||
author: Markdown PDF 导出器研发组
|
||||
---
|
||||
|
||||
# HTTP 导出保持可编辑
|
||||
|
||||
这是一段由真实 Fastify 路由和 Pandoc 生成的可编辑中文正文,包含
|
||||
[验收链接](https://example.invalid/server-docx)。
|
||||
|
||||
1. 原生编号第一项
|
||||
2. 原生编号第二项
|
||||
|
||||
| HTTP 门禁 | 预期 |
|
||||
| --- | --- |
|
||||
| MIME | DOCX |
|
||||
| 内容 | 可编辑 |
|
||||
|
||||
脚注必须保留。[^server-footnote]
|
||||
|
||||
公式 $a^2+b^2=c^2$ 必须使用 OMML。
|
||||
|
||||
[^server-footnote]: Server HTTP 验收脚注。
|
||||
`;
|
||||
const exportConfig = {
|
||||
...defaultExportConfig,
|
||||
themeId: "typora-like",
|
||||
pageDecorationsMode: "custom",
|
||||
paper: {
|
||||
...defaultExportConfig.paper,
|
||||
marginMode: "custom",
|
||||
margins: {
|
||||
top: "16mm",
|
||||
right: "16mm",
|
||||
bottom: "16mm",
|
||||
left: "16mm"
|
||||
}
|
||||
}
|
||||
};
|
||||
const mediaAdapter = {
|
||||
async capture() {
|
||||
return {
|
||||
plan: {
|
||||
targets: [],
|
||||
echartsErrors: [],
|
||||
mermaidErrors: []
|
||||
},
|
||||
captures: []
|
||||
};
|
||||
},
|
||||
async close() {}
|
||||
};
|
||||
const pdfGenerator = {
|
||||
async generate() {
|
||||
throw new Error("DOCX HTTP 验收不得调用 PDF");
|
||||
},
|
||||
async close() {}
|
||||
};
|
||||
const beforeTemporaryDirectories = temporaryDirectories();
|
||||
const app = buildApp({
|
||||
logger: false,
|
||||
pdfGenerator,
|
||||
docxMediaAdapter: mediaAdapter
|
||||
});
|
||||
|
||||
let response;
|
||||
try {
|
||||
const capabilityResponse = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/docx/capability"
|
||||
});
|
||||
assert(capabilityResponse.statusCode === 200, "DOCX capability 请求失败");
|
||||
assert(
|
||||
capabilityResponse.json().status === "available",
|
||||
`DOCX capability 不可用:${capabilityResponse.body}`
|
||||
);
|
||||
|
||||
response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/docx",
|
||||
payload: {
|
||||
markdown,
|
||||
fileName: "目录/Server HTTP 验收.md",
|
||||
language: "zh-CN",
|
||||
resources: [],
|
||||
exportConfig
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
|
||||
assert(response.statusCode === 200, `DOCX HTTP 返回 ${response.statusCode}`);
|
||||
assert(
|
||||
response.headers["content-type"]?.includes(DOCX_MIME_TYPE),
|
||||
`DOCX MIME 无效:${response.headers["content-type"]}`
|
||||
);
|
||||
assert(
|
||||
response.headers["content-disposition"]?.includes(
|
||||
"Server%20HTTP%20%E9%AA%8C%E6%94%B6.docx"
|
||||
),
|
||||
`DOCX 文件名无效:${response.headers["content-disposition"]}`
|
||||
);
|
||||
assert(
|
||||
response.headers["cache-control"] === "no-store",
|
||||
"DOCX HTTP 响应必须禁用缓存"
|
||||
);
|
||||
for (const timing of [
|
||||
"runtime-probe",
|
||||
"prepare",
|
||||
"media",
|
||||
"reference",
|
||||
"pandoc",
|
||||
"validation",
|
||||
"total"
|
||||
]) {
|
||||
assert(
|
||||
response.headers["server-timing"]?.includes(`${timing};dur=`),
|
||||
`Server-Timing 缺少 ${timing}`
|
||||
);
|
||||
}
|
||||
assert(
|
||||
response.headers["x-docx-warning-count"] === "0",
|
||||
"无媒体 HTTP 验收不应产生 DOCX 警告"
|
||||
);
|
||||
|
||||
const report = inspectDocxAcceptance(response.rawPayload, {
|
||||
id: "server-http",
|
||||
page: {
|
||||
widthTwips: 11906,
|
||||
heightTwips: 16838,
|
||||
orientation: "portrait",
|
||||
marginsTwips: {
|
||||
top: 907,
|
||||
right: 907,
|
||||
bottom: 907,
|
||||
left: 907
|
||||
}
|
||||
},
|
||||
requiredText: [
|
||||
"真实 Fastify 路由",
|
||||
"原生编号第一项",
|
||||
"HTTP 门禁"
|
||||
],
|
||||
requiredFootnoteText: ["Server HTTP 验收脚注"],
|
||||
minimumParagraphs: 8,
|
||||
minimumTextRuns: 12,
|
||||
minimumTables: 1,
|
||||
minimumNumberedParagraphs: 2,
|
||||
minimumHyperlinks: 1,
|
||||
minimumFootnoteReferences: 1,
|
||||
minimumMathObjects: 1,
|
||||
requiredStyleIds: [
|
||||
"Normal",
|
||||
"Heading1",
|
||||
"SourceCode",
|
||||
"Table",
|
||||
"Caption"
|
||||
],
|
||||
requirePageField: true
|
||||
});
|
||||
|
||||
const afterTemporaryDirectories = temporaryDirectories();
|
||||
const leakedTemporaryDirectories = [...afterTemporaryDirectories].filter(
|
||||
(name) => !beforeTemporaryDirectories.has(name)
|
||||
);
|
||||
assert(
|
||||
leakedTemporaryDirectories.length === 0,
|
||||
`DOCX HTTP 遗留临时目录:${leakedTemporaryDirectories.join(", ")}`
|
||||
);
|
||||
|
||||
fs.mkdirSync(outputDirectory, { recursive: true });
|
||||
const outputPath = path.join(outputDirectory, "server-http.docx");
|
||||
const reportPath = path.join(
|
||||
outputDirectory,
|
||||
"server-http-report.json"
|
||||
);
|
||||
fs.writeFileSync(outputPath, response.rawPayload);
|
||||
const result = {
|
||||
outputFile: path.relative(repositoryDirectory, outputPath),
|
||||
bytes: response.rawPayload.byteLength,
|
||||
headers: {
|
||||
contentType: response.headers["content-type"],
|
||||
contentDisposition: response.headers["content-disposition"],
|
||||
cacheControl: response.headers["cache-control"],
|
||||
serverTiming: response.headers["server-timing"],
|
||||
warningCount: response.headers["x-docx-warning-count"],
|
||||
echartsErrorCount: response.headers["x-echarts-error-count"],
|
||||
mermaidErrorCount: response.headers["x-mermaid-error-count"]
|
||||
},
|
||||
leakedTemporaryDirectories,
|
||||
acceptance: report
|
||||
};
|
||||
fs.writeFileSync(
|
||||
reportPath,
|
||||
`${JSON.stringify(result, null, 2)}\n`,
|
||||
"utf8"
|
||||
);
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
Reference in New Issue
Block a user