125 lines
5.9 KiB
TypeScript
125 lines
5.9 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { existsSync, mkdtempSync, readFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import test from "node:test";
|
|
import { clientConfig, runCli } from "../src/cli.js";
|
|
import { VERSION } from "../src/version.js";
|
|
|
|
test("package, manifest, and runtime versions agree with commit-pinned installation", () => {
|
|
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
const manifest = JSON.parse(readFileSync(new URL("../install-manifest.json", import.meta.url), "utf8"));
|
|
assert.equal(VERSION, "0.4.0");
|
|
assert.equal(packageJson.version, VERSION);
|
|
assert.equal(manifest.version, VERSION);
|
|
assert.equal(manifest.schemaVersion, 3);
|
|
assert.equal(manifest.distribution.branch, "main");
|
|
assert.equal(manifest.distribution.strategy, "resolve-latest-commit-then-pin");
|
|
assert.equal(manifest.distribution.releaseRequired, false);
|
|
assert.equal(manifest.distribution.releaseRef, undefined);
|
|
assert.equal(manifest.installation.temporarySourceArchiveBuild, true);
|
|
assert.equal(manifest.installation.fallbackToRelease, false);
|
|
assert.equal(manifest.entrypoints.guidedCommand, "client-config");
|
|
assert.equal(manifest.entrypoints.mcpLoginTool, "nocobase_login");
|
|
assert.equal(manifest.entrypoints.mcpLoginStatusTool, "nocobase_login_status");
|
|
assert.deepEqual(packageJson.dependencies, undefined);
|
|
assert.equal(manifest.runtime.runtimeNpmDependencies, 0);
|
|
});
|
|
|
|
test("installation protocol resolves main once and never falls back to an old Release", () => {
|
|
const guide = readFileSync(new URL("../INSTALL.md", import.meta.url), "utf8");
|
|
const readme = readFileSync(new URL("../README.md", import.meta.url), "utf8");
|
|
const installUrl = "https://git.justconn.top/gitea-admin/nocobase-mcp/raw/branch/main/INSTALL.md";
|
|
assert.match(guide, /branches\/main/);
|
|
assert.match(guide, /40 位十六进制完整 SHA/);
|
|
assert.match(guide, /resolve-latest-commit-then-pin/);
|
|
assert.match(guide, /archive\/<commit-sha>\.zip/);
|
|
assert.match(guide, /archive\/<commit-sha>\.tar\.gz/);
|
|
assert.match(guide, /Windows 使用 `.zip`/);
|
|
assert.match(guide, /不得.*退回旧 Release/);
|
|
assert.match(guide, /client-config --json/);
|
|
assert.match(guide, /nocobase_login\(\{\}\)/);
|
|
assert.ok(guide.includes(installUrl));
|
|
assert.ok(readme.includes(installUrl));
|
|
assert.match(readme, /nocobase_login/);
|
|
assert.doesNotMatch(guide, /下载本文件所在的同一固定 Release/);
|
|
assert.doesNotMatch(readme, /<main 分支的 INSTALL\.md 链接>/);
|
|
});
|
|
|
|
test("generated MCP config contains no Key, token, or environment secret", () => {
|
|
const config = clientConfig("/private/config.json", false);
|
|
const serialized = JSON.stringify(config);
|
|
assert.match(serialized, /nocobase-crm/);
|
|
assert.match(serialized, /config\.json/);
|
|
assert.doesNotMatch(serialized, /authorize|token|secret|password/i);
|
|
});
|
|
|
|
test("manual Key fallback refuses a non-TTY before creating credential files", async () => {
|
|
if (process.stdin.isTTY || process.stderr.isTTY) return;
|
|
const directory = mkdtempSync(join(tmpdir(), "nocobase-mcp-nontty-"));
|
|
const path = join(directory, "credentials.json");
|
|
const previous = process.env.NOCOBASE_MCP_CREDENTIALS;
|
|
process.env.NOCOBASE_MCP_CREDENTIALS = path;
|
|
try {
|
|
await assert.rejects(runCli(["setup", "--manual-key", "--json"]), /local terminal/);
|
|
assert.equal(existsSync(path), false);
|
|
assert.equal(existsSync(join(directory, "config.json")), false);
|
|
} finally {
|
|
if (previous === undefined) delete process.env.NOCOBASE_MCP_CREDENTIALS;
|
|
else process.env.NOCOBASE_MCP_CREDENTIALS = previous;
|
|
}
|
|
});
|
|
|
|
test("setup rejects a missing explicit config before starting browser authorization", async () => {
|
|
const directory = mkdtempSync(join(tmpdir(), "nocobase-mcp-setup-preflight-"));
|
|
const credentials = join(directory, "credentials.json");
|
|
const missingConfig = join(directory, "missing-config.json");
|
|
const previousCredentials = process.env.NOCOBASE_MCP_CREDENTIALS;
|
|
process.env.NOCOBASE_MCP_CREDENTIALS = credentials;
|
|
try {
|
|
await assert.rejects(
|
|
runCli(["setup", "--config", missingConfig, "--no-browser", "--json"]),
|
|
/Config file does not exist/,
|
|
);
|
|
assert.equal(existsSync(credentials), false);
|
|
} finally {
|
|
if (previousCredentials === undefined) delete process.env.NOCOBASE_MCP_CREDENTIALS;
|
|
else process.env.NOCOBASE_MCP_CREDENTIALS = previousCredentials;
|
|
}
|
|
});
|
|
|
|
test("client-config prepares the bundled default config before login", async () => {
|
|
const directory = mkdtempSync(join(tmpdir(), "nocobase-mcp-client-config-"));
|
|
const credentials = join(directory, "credentials.json");
|
|
const config = join(directory, "config.json");
|
|
const previousCredentials = process.env.NOCOBASE_MCP_CREDENTIALS;
|
|
const previousConfig = process.env.NOCOBASE_MCP_CONFIG;
|
|
const originalWrite = process.stdout.write;
|
|
let output = "";
|
|
process.env.NOCOBASE_MCP_CREDENTIALS = credentials;
|
|
delete process.env.NOCOBASE_MCP_CONFIG;
|
|
process.stdout.write = ((chunk: string | Uint8Array) => {
|
|
output += String(chunk);
|
|
return true;
|
|
}) as typeof process.stdout.write;
|
|
try {
|
|
await runCli(["client-config", "--json"]);
|
|
assert.equal(existsSync(config), true);
|
|
assert.equal(existsSync(credentials), false);
|
|
assert.equal(JSON.parse(output).mcpServers["nocobase-crm"].args.includes("--config"), true);
|
|
} finally {
|
|
process.stdout.write = originalWrite;
|
|
if (previousCredentials === undefined) delete process.env.NOCOBASE_MCP_CREDENTIALS;
|
|
else process.env.NOCOBASE_MCP_CREDENTIALS = previousCredentials;
|
|
if (previousConfig === undefined) delete process.env.NOCOBASE_MCP_CONFIG;
|
|
else process.env.NOCOBASE_MCP_CONFIG = previousConfig;
|
|
}
|
|
});
|
|
|
|
test("setup rejects API endpoint overrides", async () => {
|
|
await assert.rejects(
|
|
runCli(["setup", "--api-base-url", "https://other.example"]),
|
|
/Unknown option: --api-base-url/,
|
|
);
|
|
});
|