Files
dingMCP/test/distribution.test.ts
T

127 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import test from "node:test";
test("customer distribution prefers remote MCP OAuth and keeps a generic compatibility bridge", () => {
const packageMetadata = JSON.parse(readFileSync("package.json", "utf8")) as { version: string };
assert.match(packageMetadata.version, /^\d+\.\d+\.\d+-rc\.\d+$/);
assert.equal(existsSync("plugins/dingtalk-approval"), false);
const installer = readFileSync("distribution/agent-install.template.md", "utf8");
assert.match(installer, /远程.*Streamable HTTP MCP/s);
assert.match(installer, /URL:`\{\{GATEWAY_URL\}\}`/);
assert.match(installer, /认证:OAuth/);
assert.match(installer, /动态客户端注册/);
assert.match(installer, /不要安装 Node\.js、npm、本地 Bridge/);
assert.match(installer, /只有.*不支持远程 Streamable HTTP MCP.*标准 OAuth.*兼容/s);
assert.match(installer, /COMPAT_INSTALL_URL/);
assert.match(installer, /approval_list_todos.*source="all".*limit=1/s);
assert.match(installer, /钉钉 OA 与薪福通来源都为 `ok`/);
assert.doesNotMatch(installer, /PowerShell|Git Bash|stdio|postInstall|loginCommand|npm install -g/);
assert.doesNotMatch(installer, /--host|WorkBuddy|Codex|Claude/);
const compatibilityInstaller = readFileSync("distribution/agent-compat-install.template.md", "utf8");
assert.match(compatibilityInstaller, /仅当.*不支持远程 Streamable HTTP MCP.*标准 OAuth/s);
assert.match(compatibilityInstaller, /Ed25519/);
assert.match(compatibilityInstaller, /使用 `mcpConfig`/);
assert.match(compatibilityInstaller, /Windows(PowerShell)/);
assert.match(compatibilityInstaller, /curl\.exe[\s\S]*Invoke-WebRequest[\s\S]*Get-FileHash[\s\S]*ConvertFrom-Json/);
assert.match(compatibilityInstaller, /90 秒[\s\S]*stderr[\s\S]*stdout/);
assert.match(compatibilityInstaller, /登录链接过期.*绝不能重新安装.*approval_login.*loginCommand/s);
assert.match(compatibilityInstaller, /sourceStatus[\s\S]*dingtalk\.status[\s\S]*xft\.status[\s\S]*都必须是 `ok`/);
assert.match(compatibilityInstaller, /更新[\s\S]*update --json[\s\S]*卸载[\s\S]*uninstall --yes --json/);
assert.doesNotMatch(compatibilityInstaller, /TimeoutSec 300|--max-time 300|--timeout=300|至少 10 分钟超时/);
assert.doesNotMatch(compatibilityInstaller, /--host|WorkBuddy|Codex|Claude/);
assert.doesNotMatch(compatibilityInstaller, /DWS_CLI_PATH|dingtalk-workspace-cli|APP_MODE=local/);
const runtime = JSON.parse(readFileSync("distribution/node-runtime.json", "utf8"));
assert.equal(runtime.version, "22.23.2");
assert.deepEqual(Object.keys(runtime.platforms).sort(), [
"darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64",
]);
assert.equal(runtime.sourceBases.length, 3);
assert.ok(runtime.sourceBases.every((value: string) => value.includes(`/v${runtime.version}/`) && !value.includes("latest")));
assert.ok(Object.values(runtime.platforms).every((value) => /^[a-f0-9]{64}$/.test((value as { sha256: string }).sha256)));
const windowsBootstrap = readFileSync("distribution/bootstrap-windows.template.ps1", "utf8");
const posixBootstrap = readFileSync("distribution/bootstrap-posix.template.sh", "utf8");
assert.match(windowsBootstrap, /OSArchitecture[\s\S]*Save-DingDownload[\s\S]*Expand-Archive/);
assert.match(windowsBootstrap, /SHA-256 不匹配,停止安装/);
assert.match(windowsBootstrap, /Write-DingProgress[\s\S]*\[dingtalk-approval\]/);
assert.match(windowsBootstrap, /dingInstallerUrls[\s\S]*dingManifestMirrorUrls[\s\S]*use-env-proxy/s);
assert.match(windowsBootstrap, /curl\.exe[\s\S]*--connect-timeout 10[\s\S]*--max-time 90/);
assert.match(windowsBootstrap, /-e "const \[a,b\]=process\.versions\.node\.split\('\.'\)/);
assert.match(windowsBootstrap, /-e "process\.exit\(process\.allowedNodeEnvironmentFlags\.has\('--use-env-proxy'\)/);
assert.doesNotMatch(windowsBootstrap, /TimeoutSec 300|-e '.*split\("\."\)/);
assert.match(posixBootstrap, /^#!\/bin\/sh\nset -eu/);
assert.match(posixBootstrap, /uname -s[\s\S]*uname -m[\s\S]*tar -xzf/);
assert.match(posixBootstrap, /SHA-256 不匹配,停止安装/);
assert.match(posixBootstrap, /ding_progress[\s\S]*\[dingtalk-approval\]/);
assert.match(posixBootstrap, /ding_installer_urls[\s\S]*ding_manifest_mirror_urls[\s\S]*use-env-proxy/s);
assert.match(posixBootstrap, /--connect-timeout 10[\s\S]*--max-time 90[\s\S]*--speed-time 20/);
assert.doesNotMatch(posixBootstrap, /--max-time 300|--timeout=300/);
assert.equal(spawnSync("sh", ["-n", "distribution/bootstrap-posix.template.sh"]).status, 0);
assert.doesNotMatch(`${windowsBootstrap}\n${posixBootstrap}`, /latest-v|npm install -g/);
const skill = readFileSync("skills/dingtalk-approval/SKILL.md", "utf8");
const createApproval = readFileSync("skills/dingtalk-approval/references/create-approval.md", "utf8");
assert.match(skill, /read `references\/create-approval\.md` completely/);
assert.doesNotMatch(skill, /XFT recall is experimental/);
assert.match(createApproval, /XFT recall is experimental.*XFTSYS9999/s);
assert.match(skill, /host's native remote-MCP OAuth flow by default/);
assert.match(skill, /both DingTalk OA and XFT must be available/);
assert.match(skill, /Only hosts that cannot use remote Streamable HTTP MCP or standard OAuth.*local STDIO Bridge/s);
assert.match(skill, /authChallenge\.directUrl.*current reply/s);
assert.match(skill, /onboarding\.firstRun=true.*Do not execute an example automatically/s);
assert.match(skill, /link expires.*approval_login.*Never reinstall/s);
const builder = readFileSync("scripts/build-release.mjs", "utf8");
assert.match(builder, /schemaVersion: 3,\s+version,/s);
assert.match(builder, /download-mirror-base-url[\s\S]*manifestMirrors[\s\S]*mirrors:/s);
assert.match(builder, /signData\(null, manifestBytes/);
assert.match(builder, /dingtalk-approval-client/);
assert.match(builder, /bootstrap-windows\.template\.ps1/);
assert.match(builder, /bootstrap-posix\.template\.sh/);
assert.match(builder, /agent-compat-install\.template\.md/);
assert.match(builder, /compat-install\.md/);
assert.match(builder, /utf8Bom: true/);
assert.match(builder, /validateNodeRuntime/);
assert.match(builder, /cpSync\(resolve\("skills\/dingtalk-approval"\).*recursive: true/s);
assert.doesNotMatch(builder, /codexPlugin|codex-plugin|\.codex-plugin|openai\.yaml/);
assert.doesNotMatch(builder, /dingtalk-workspace-cli|DWS_URL|DWS_SHA256/);
const deploy = readFileSync("deploy/install.sh", "utf8");
assert.match(deploy, /--download-base-url "\$\{gitea_release_base_url\}"[\s\S]*--download-mirror-base-url "\$\{public_download_base_url\}"/s);
assert.match(deploy, /refs\/tags\/\$\{release_tag\}[\s\S]*release_created_at_commit="\$\{release_tag_commit:-\$\{release_commit\}\}"/s);
assert.match(deploy, /! -name 'agent-test-install\.md' ! -name 'dingmcp-test-bundle\.tar\.gz'/);
const testBundleBuilder = readFileSync("scripts/build-test-bundle.mjs", "utf8");
assert.match(testBundleBuilder, /--sort=name.*--mtime=@0.*--owner=0.*--group=0.*--numeric-owner.*--format=ustar/s);
});
test("public install page explicitly declares UTF-8 for browser rendering", () => {
const nginx = readFileSync("deploy/approval.justconn.top.nginx.conf", "utf8");
for (const name of ["install", "compat-install"]) {
const location = nginx.match(new RegExp(`location = /downloads/dingmcp/${name}\\.md \\{([\\s\\S]*?)\\n \\}`))?.[1] ?? "";
assert.match(location, /default_type text\/markdown;/);
assert.match(location, /charset utf-8;/);
assert.match(location, /charset_types text\/markdown;/);
}
});
test("Gitea release lookup tolerates tea field-name corruption and fails closed", () => {
const lookup = (body: string, tag = "v1.1.0-rc.11") => spawnSync(
process.execPath,
["scripts/release-tag-exists.mjs", tag],
{ input: body, encoding: "utf8" },
);
assert.equal(lookup('[{"tag_name":"v1.1.0-rc.11"}]').status, 0);
assert.equal(lookup('[{"tag-_name":"v1.1.0-rc.11"}]').status, 0);
assert.equal(lookup('{"data":[{"Tag":"v1.1.0-rc.11"}]}').status, 0);
assert.equal(lookup('[{"tag-_name":"v1.1.0-rc.10"}]').status, 1);
const invalid = lookup("not json");
assert.equal(invalid.status, 2);
assert.match(invalid.stderr, /无法解析/);
});