Files
rea/tests/acceptance/setup/dshClientSetup.test.ts
T
morluto 21eff3bae1 fix(setup): preserve Cordis ownership across uncertain patch structure
Separate stable registration ownership from setup editability. Keep group-owned rows removable, retain expression-backed structures without edits, and reject malformed literal patches before setup writes. Preserve foreign rows and backups during uninstall.

Correct stale alias fixtures to use valid Harness patch rows and remove redundant alias coverage. Validation: 78 configuration boundary tests, 6 public setup/uninstall CLI tests, full static checks, and docs checks passed. Harness behavior was verified from pinned source; no real Harness runtime verification is claimed.
2026-10-12 11:59:23 +08:00

300 lines
10 KiB
TypeScript

import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { expect } from "vitest";
import { parse } from "yaml";
import { z } from "zod";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest(
"DSH CLI previews, approves, repairs, launches MCP, repeats and uninstalls while preserving caller YAML",
async ({ cli }) => {
const home = await createTestTempDirectory("rea-dsh-lifecycle-");
const profile = join(home, "selected harness");
const config = join(profile, "cordis.patch.yml");
const skill = join(
home,
".agents/skills/reverse-engineer-anything/SKILL.md",
);
await mkdir(profile);
const original = `# caller home patch
- insert:
- id: rea-mcp
name: '@deepseek-ai/dsh-mcp-client'
config: {serverName: rea, transport: stdio, command: stale, args: [mcp]}
- holder: &op
insert:
- id: unrelated
name: caller-plugin
disabled: true
config: {cwd: !!js process.cwd()}
- id: rea-mcp
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: rea
transport: stdio
command: stale
args: [mcp]
cwd: !!js process.cwd()
env:
REA_LOG_LEVEL: !!js process.env.REA_LOG_LEVEL
- *op
`;
await writeFile(config, original);
const environment = { HOME: home, USERPROFILE: home, DSH_HOME: profile };
const run = (args: readonly string[]) =>
cli.run({
arguments: [...args, "--json"],
cwd: home,
environment,
timeoutMs: 20_000,
});
const plan = await run(["setup", "--client", "dsh", "--dry-run"]);
expect(plan.exitCode).toBe(0);
expect(plan.json).toMatchObject({
status: "planned",
appliedActions: [],
plannedActions: expect.arrayContaining([
expect.objectContaining({ id: "configure_client:dsh", target: config }),
expect.objectContaining({ id: "install_skill" }),
]),
});
expect(await readFile(config, "utf8")).toBe(original);
await expect(readFile(`${config}.rea.backup`)).rejects.toMatchObject({
code: "ENOENT",
});
const unapproved = await run(["setup", "--client", "dsh"]);
expect(unapproved.json).toMatchObject({
status: "needs_confirmation",
appliedActions: [],
});
expect(await readFile(config, "utf8")).toBe(original);
const configured = await run(["setup", "--client", "dsh", "--yes"]);
expect(configured.exitCode).toBe(0);
expect(configured.json).toMatchObject({
status: "ready",
clients: { dsh: { status: "configured" } },
});
const text = await readFile(config, "utf8");
expect(text).toContain("# caller home patch");
const patches = parse(text, {
customTags: [
{
tag: "tag:yaml.org,2002:js",
resolve: (source: string) => ({ __jsExpr: source }),
},
],
}) as { insert?: { id: string; config: unknown }[] }[];
const rows = patches.flatMap((patch) => patch.insert ?? []);
expect(rows.filter(({ id }) => id === "unrelated")).toEqual([
{
id: "unrelated",
name: "caller-plugin",
disabled: true,
config: { cwd: { __jsExpr: "process.cwd()" } },
},
]);
expect(rows.filter(({ id }) => id === "rea-mcp")).toHaveLength(1);
const registration = z
.object({
command: z.string(),
args: z.array(z.string()),
cwd: z.unknown(),
env: z.unknown(),
})
.parse(rows.find(({ id }) => id === "rea-mcp")?.config);
expect(registration.cwd).toEqual({ __jsExpr: "process.cwd()" });
expect(registration.env).toMatchObject({
REA_LOG_LEVEL: { __jsExpr: "process.env.REA_LOG_LEVEL" },
});
const doctor = await run(["doctor", "--client", "dsh", "--skill"]);
expect(doctor.exitCode).toBe(0);
expect(doctor.json).toMatchObject({
healthy: true,
identity: {
registrations: expect.arrayContaining([
expect.objectContaining({ client: "dsh", state: "aligned" }),
]),
},
});
await verifyGeneratedLauncher(registration, home, environment);
const repeated = await run(["setup", "--client", "dsh", "--yes"]);
expect(repeated.json).toMatchObject({
status: "ready",
appliedActions: [],
plannedActions: [],
});
expect(await readFile(config, "utf8")).toBe(text);
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
const removed = await run(["uninstall"]);
expect(removed.exitCode).toBe(0);
expect(removed.json).toMatchObject({
status: "complete",
items: expect.arrayContaining([
expect.objectContaining({ name: "dsh", status: "removed" }),
]),
});
const remaining = parse(await readFile(config, "utf8"), {
logLevel: "silent",
}) as { insert?: { id: string }[] }[];
expect(
remaining.flatMap((patch) => patch.insert ?? []).map(({ id }) => id),
).toEqual(["unrelated"]);
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
await expect(readFile(skill)).rejects.toMatchObject({ code: "ENOENT" });
},
);
async function verifyGeneratedLauncher(
registration: { command: string; args: string[] },
home: string,
environment: Record<string, string>,
): Promise<void> {
// Exercise the generated launcher. Native Cordis expression evaluation belongs
// to verify:deepseek-client, not this SDK transport journey.
const transport = new StdioClientTransport({
command: registration.command,
args: registration.args,
cwd: home,
env: { PATH: process.env.PATH ?? "", ...environment },
stderr: "pipe",
});
const client = new Client({ name: "dsh-setup-lifecycle", version: "1" });
try {
await client.connect(transport);
expect((await client.listTools()).tools.map(({ name }) => name)).toContain(
"binary_session",
);
const session = await client.callTool({
name: "binary_session",
arguments: {},
});
expect(session.isError).not.toBe(true);
expect(session.structuredContent).toMatchObject({
result: { open: false },
});
} finally {
await client.close();
await transport.close();
}
}
cliTest.for(["null", "caller-value", "[caller-value]"])(
"DSH CLI refuses to edit config %s and plain uninstall removes its owned row",
async (configValue, { cli }) => {
const home = await createTestTempDirectory("rea-dsh-malformed-config-");
const profile = join(home, ".dsh");
const config = join(profile, "cordis.patch.yml");
await mkdir(profile);
const initial = `# caller patch
- insert:
- id: other
name: caller-plugin
- id: rea-mcp
name: '@deepseek-ai/dsh-mcp-client'
config: ${configValue}
`;
await writeFile(config, initial);
const run = (args: readonly string[]) =>
cli.run({
arguments: [...args, "--json"],
cwd: home,
environment: { HOME: home, USERPROFILE: home, DSH_HOME: profile },
});
const configured = await run(["setup", "--client", "dsh", "--yes"]);
expect(configured.exitCode).not.toBe(0);
expect(configured.json).toMatchObject({
status: "needs_human",
appliedActions: [],
remediation: expect.stringContaining("config: Use a literal mapping"),
});
expect(await readFile(config, "utf8")).toBe(initial);
await expect(readFile(`${config}.rea.backup`)).rejects.toMatchObject({
code: "ENOENT",
});
const removed = await run(["uninstall"]);
expect(removed.exitCode).toBe(0);
expect(removed.json).toMatchObject({
status: "complete",
items: expect.arrayContaining([
expect.objectContaining({ name: "dsh", status: "removed" }),
]),
});
const remaining = await readFile(config, "utf8");
expect(remaining).toContain("# caller patch");
expect(parse(remaining)).toEqual([
{ insert: [{ id: "other", name: "caller-plugin" }] },
]);
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(initial);
},
);
cliTest.for([
{
label: "group-owned rows",
patch: `- id: tools
insert:
- id: other
name: caller-plugin
- id: rea-mcp
name: '@deepseek-ai/dsh-mcp-client'
config: {serverName: rea, transport: stdio, command: stale, args: [mcp]}
`,
remediation: "group with unknown activity",
removal: "removed",
},
{
label: "expression-backed inserts",
patch: "- insert: !!js process.env.DSH_ROWS\n",
remediation: "unknown structure",
removal: "retained",
},
])(
"DSH CLI rejects setup and preserves ownership for $label",
async ({ patch, remediation, removal }, { cli }) => {
const home = await createTestTempDirectory("rea-dsh-ownership-");
const profile = join(home, ".dsh");
const config = join(profile, "cordis.patch.yml");
await mkdir(profile);
await writeFile(config, patch);
const run = (args: readonly string[]) =>
cli.run({
arguments: [...args, "--json"],
cwd: home,
environment: { HOME: home, USERPROFILE: home, DSH_HOME: profile },
});
const setup = await run(["setup", "--client", "dsh", "--yes"]);
expect(setup.exitCode).not.toBe(0);
expect(setup.json).toMatchObject({
status: "needs_human",
appliedActions: [],
remediation: expect.stringContaining(remediation),
});
expect(await readFile(config, "utf8")).toBe(patch);
await expect(readFile(`${config}.rea.backup`)).rejects.toMatchObject({
code: "ENOENT",
});
const uninstall = await run(["uninstall"]);
expect(uninstall.exitCode).toBe(0);
expect(uninstall.json).toMatchObject({
status: "complete",
items: expect.arrayContaining([
expect.objectContaining({ name: "dsh", status: removal }),
]),
});
if (removal === "removed") {
expect(parse(await readFile(config, "utf8"))).toEqual([
{ id: "tools", insert: [{ id: "other", name: "caller-plugin" }] },
]);
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(patch);
} else {
expect(await readFile(config, "utf8")).toBe(patch);
await expect(readFile(`${config}.rea.backup`)).rejects.toMatchObject({
code: "ENOENT",
});
}
},
);