Files
dingMCP/test/admin.test.ts
T
gitea-admin 504145aec6
CI / Client installer (macos-latest) (push) Canceled after 0s
CI / Client installer (ubuntu-latest) (push) Canceled after 0s
CI / Client installer (windows-latest) (push) Canceled after 0s
CI / test (22) (push) Canceled after 0s
CI / test (24) (push) Canceled after 0s
CI / release-smoke (push) Canceled after 0s
feat: initial public release
2026-08-17 03:04:09 +00:00

72 lines
3.8 KiB
TypeScript

import assert from "node:assert/strict";
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { configureService, renderUnit, servicePaths, startService, uninstallService } from "../src/admin.js";
test("dingtalk-admin writes remote OAuth settings and secret-free hardened unit", () => {
const root = mkdtempSync(join(tmpdir(), "ding-admin-"));
const old = process.env.XDG_CONFIG_HOME;
process.env.XDG_CONFIG_HOME = join(root, "config");
try {
mkdirSync(join(root, ".secrets"), { mode: 0o700 });
writeFileSync(join(root, ".secrets", "dingtalk.json"), "{}", { mode: 0o600 });
writeFileSync(join(root, ".secrets", "xft.key"), "secret", { mode: 0o600 });
chmodSync(join(root, ".secrets", "dingtalk.json"), 0o600);
const paths = configureService({
projectRoot: root, publicBaseUrl: "https://approval.example.com", corpId: "ding-corp", port: 3000,
credentialsFile: ".secrets/dingtalk.json", xftOpenApiAppId: "app",
xftOpenApiSecretFile: ".secrets/xft.key", xftOpenApiEnterpriseId: "enterprise",
dingtalkClientOnlyProcessCodes: "PROC-SIGN,PROC-CLIENT",
});
assert.equal(statSync(paths.env).mode & 0o777, 0o600);
assert.equal(statSync(paths.refSecret).mode & 0o777, 0o600);
assert.equal(statSync(paths.sessionSecret).mode & 0o777, 0o600);
const runtime = readFileSync(paths.env, "utf8");
assert.match(runtime, /MCP_REMOTE_AUTH_ENABLED="true"/);
assert.match(runtime, /DINGTALK_CORP_ID="ding-corp"/);
assert.match(runtime, /MCP_AUTH_DATABASE=/);
assert.match(runtime, /APP_MODE="gateway"/);
assert.match(runtime, /DINGTALK_CLIENT_ONLY_PROCESS_CODES="PROC-SIGN,PROC-CLIENT"/);
assert.doesNotMatch(runtime, /DWS_/);
assert.doesNotMatch(runtime, /MCP_TRUSTED_LOCAL_USER_ID|CLIENT_SECRET/);
const unit = renderUnit(paths, "/usr/bin/node");
assert.match(unit, /ProtectSystem=strict/);
assert.doesNotMatch(unit, /ding-corp|CLIENT_SECRET/);
mkdirSync(join(root, "dist", "src"), { recursive: true });
writeFileSync(join(root, "dist", "src", "index.js"), "// built");
const calls: string[][] = [];
startService(root, (args) => calls.push(args));
assert.deepEqual(calls.map((call) => call[0]), ["daemon-reload", "enable", "restart"]);
uninstallService(root, (args) => calls.push(args));
assert.equal(existsSync(paths.env), false);
assert.equal(existsSync(paths.refSecret), true);
assert.equal(existsSync(paths.sessionSecret), true);
} finally {
if (old === undefined) delete process.env.XDG_CONFIG_HOME; else process.env.XDG_CONFIG_HOME = old;
}
});
test("dingtalk-admin requires HTTPS and references XFT secret files", () => {
const root = mkdtempSync(join(tmpdir(), "ding-admin-openapi-"));
mkdirSync(join(root, ".secrets"), { mode: 0o700 });
writeFileSync(join(root, ".secrets", "dingtalk.json"), "{}", { mode: 0o600 });
writeFileSync(join(root, ".secrets", "xft.key"), "1".repeat(32), { mode: 0o600 });
assert.throws(() => configureService({
projectRoot: root, publicBaseUrl: "http://approval.example.com", corpId: "ding-corp", port: 3000,
credentialsFile: ".secrets/dingtalk.json",
}), /HTTPS/);
const paths = configureService({
projectRoot: root, publicBaseUrl: "https://approval.example.com", corpId: "ding-corp", port: 3000,
credentialsFile: ".secrets/dingtalk.json", xftOpenApiAppId: "open-app",
xftOpenApiSecretFile: ".secrets/xft.key", xftOpenApiEnterpriseId: "TEST0001",
});
const runtime = readFileSync(paths.env, "utf8");
assert.match(runtime, /XFT_OPEN_API_ENABLED="true"/);
assert.match(runtime, /XFT_OPEN_API_AUTHORITY_SECRET_FILE=/);
assert.doesNotMatch(runtime, new RegExp("1".repeat(32)));
});