Treat only ENOENT as absence. Preserve unreadable metadata errors and ignore untrusted workspace content before parsing it. Refs: #3206, #3319 Co-authored-by: haoran20100322 <haoran20100322@outlook.com>
221 lines
7.8 KiB
TypeScript
221 lines
7.8 KiB
TypeScript
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import { expect } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest, type TestCli } from "../../support/cli/cliFixture.js";
|
|
|
|
const setupArguments = ["setup", "--client", "gemini_cli", "--yes", "--json"];
|
|
const doctorArguments = ["doctor", "--client", "gemini_cli", "--json"];
|
|
|
|
const geminiWorkspace = async (cli: TestCli) => {
|
|
const home = await createTestTempDirectory("rea-gemini-workspace-trust-");
|
|
const project = join(home, "project");
|
|
const workspace = join(project, ".gemini/settings.json");
|
|
const user = join(home, ".gemini/settings.json");
|
|
await mkdir(dirname(workspace), { recursive: true });
|
|
const environment: NodeJS.ProcessEnv = {
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
GEMINI_CLI_HOME: home,
|
|
GEMINI_CLI_SYSTEM_SETTINGS_PATH: join(home, "system.json"),
|
|
GEMINI_CLI_SYSTEM_DEFAULTS_PATH: join(home, "defaults.json"),
|
|
GEMINI_CLI_TRUSTED_FOLDERS_PATH: join(home, "trust.json"),
|
|
};
|
|
const initial = await cli.run({
|
|
arguments: setupArguments,
|
|
cwd: home,
|
|
environment,
|
|
});
|
|
expect(initial.exitCode, initial.stdout).toBe(0);
|
|
const originalUser = await readFile(user);
|
|
const run = (arguments_: readonly string[]) =>
|
|
cli.run({ arguments: arguments_, cwd: project, environment });
|
|
return { workspace, user, originalUser, environment, run };
|
|
};
|
|
|
|
cliTest(
|
|
"Gemini does not require workspace trust until workspace settings exist",
|
|
async ({ cli }) => {
|
|
const fixture = await geminiWorkspace(cli);
|
|
const trust = join(dirname(fixture.user), "trustedFolders.json");
|
|
fixture.environment.GEMINI_CLI_TRUSTED_FOLDERS_PATH = trust;
|
|
for (const text of ["{}", "{ not json"]) {
|
|
await writeFile(trust, text);
|
|
const setup = await fixture.run(setupArguments);
|
|
expect(setup.exitCode, setup.stdout).toBe(0);
|
|
expect(setup.json).toMatchObject({ status: "ready", appliedActions: [] });
|
|
const doctor = await fixture.run(doctorArguments);
|
|
expect(doctor.exitCode, doctor.stdout).toBe(0);
|
|
expect(doctor.json).toMatchObject({
|
|
healthy: true,
|
|
identity: {
|
|
registrations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
client: "gemini_cli",
|
|
state: "aligned",
|
|
remediation: null,
|
|
}),
|
|
]),
|
|
},
|
|
});
|
|
await expect(access(fixture.workspace)).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
expect(await readFile(trust, "utf8")).toBe(text);
|
|
expect(await readFile(fixture.user)).toEqual(fixture.originalUser);
|
|
}
|
|
|
|
const workspaceText = "{ not json";
|
|
await writeFile(fixture.workspace, workspaceText);
|
|
const setup = await fixture.run(setupArguments);
|
|
expect(setup.exitCode, setup.stdout).toBe(1);
|
|
expect(setup.json).toMatchObject({
|
|
status: "needs_human",
|
|
plannedActions: [],
|
|
appliedActions: [],
|
|
remediation: expect.stringContaining(trust),
|
|
});
|
|
const doctor = await fixture.run(doctorArguments);
|
|
expect(doctor.exitCode, doctor.stdout).toBe(1);
|
|
expect(doctor.json).toMatchObject({
|
|
healthy: false,
|
|
identity: {
|
|
registrations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
client: "gemini_cli",
|
|
state: "invalid",
|
|
remediation: expect.stringContaining(trust),
|
|
}),
|
|
]),
|
|
},
|
|
});
|
|
expect(await readFile(fixture.workspace, "utf8")).toBe(workspaceText);
|
|
expect(await readFile(trust, "utf8")).toBe("{ not json");
|
|
expect(await readFile(fixture.user)).toEqual(fixture.originalUser);
|
|
},
|
|
);
|
|
|
|
for (const scenario of [
|
|
{ name: "an unlisted workspace", environment: {} },
|
|
{
|
|
name: "an explicitly untrusted workspace",
|
|
environment: { GEMINI_CLI_TRUST_WORKSPACE: "false" },
|
|
},
|
|
{
|
|
name: "a restricted workspace with an explicit trust opt-in",
|
|
environment: {
|
|
GEMINI_CLI_TRUST_WORKSPACE: "true",
|
|
GEMINI_RESTRICTED_MODE: "true",
|
|
},
|
|
},
|
|
])
|
|
cliTest(
|
|
`Gemini setup and doctor ignore malformed settings in ${scenario.name}`,
|
|
async ({ cli }) => {
|
|
const fixture = await geminiWorkspace(cli);
|
|
Object.assign(fixture.environment, scenario.environment);
|
|
const malformed = Buffer.from("{ not json");
|
|
await writeFile(fixture.workspace, malformed);
|
|
|
|
const setup = await fixture.run(setupArguments);
|
|
expect(setup.exitCode, setup.stdout).toBe(0);
|
|
expect(setup.json).toMatchObject({ status: "ready", appliedActions: [] });
|
|
const doctor = await fixture.run(doctorArguments);
|
|
expect(doctor.exitCode, doctor.stdout).toBe(0);
|
|
expect(doctor.json).toMatchObject({
|
|
healthy: true,
|
|
identity: {
|
|
registrations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
client: "gemini_cli",
|
|
state: "aligned",
|
|
remediation: null,
|
|
}),
|
|
]),
|
|
},
|
|
});
|
|
expect(await readFile(fixture.workspace)).toEqual(malformed);
|
|
expect(await readFile(fixture.user)).toEqual(fixture.originalUser);
|
|
await expect(
|
|
access(`${fixture.workspace}.rea.backup`),
|
|
).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
|
|
if (scenario.name === "an explicitly untrusted workspace") {
|
|
const excluded = Buffer.from(
|
|
JSON.stringify({ mcp: { excluded: ["rea"] } }),
|
|
);
|
|
await writeFile(fixture.workspace, excluded);
|
|
const excludedSetup = await fixture.run(setupArguments);
|
|
expect(excludedSetup.exitCode, excludedSetup.stdout).toBe(0);
|
|
const excludedDoctor = await fixture.run(doctorArguments);
|
|
expect(excludedDoctor.exitCode, excludedDoctor.stdout).toBe(0);
|
|
expect(excludedDoctor.json).toMatchObject({
|
|
identity: {
|
|
registrations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
client: "gemini_cli",
|
|
state: "aligned",
|
|
}),
|
|
]),
|
|
},
|
|
});
|
|
expect(await readFile(fixture.workspace)).toEqual(excluded);
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"Gemini still rejects malformed settings and exclusions in a trusted workspace",
|
|
async ({ cli }) => {
|
|
const fixture = await geminiWorkspace(cli);
|
|
fixture.environment.GEMINI_CLI_TRUST_WORKSPACE = "true";
|
|
for (const scenario of [
|
|
{
|
|
text: "{ not json",
|
|
state: "invalid",
|
|
diagnostic: "Invalid JSON/JSONC",
|
|
},
|
|
{
|
|
text: JSON.stringify({ mcp: { excluded: ["rea"] } }),
|
|
state: "stale",
|
|
diagnostic: "mcp.excluded",
|
|
},
|
|
]) {
|
|
const original = Buffer.from(scenario.text);
|
|
await writeFile(fixture.workspace, original);
|
|
const setup = await fixture.run(setupArguments);
|
|
expect(setup.exitCode, setup.stdout).toBe(1);
|
|
expect(setup.json).toMatchObject({
|
|
status: "needs_human",
|
|
plannedActions: [],
|
|
appliedActions: [],
|
|
remediation: expect.stringContaining(scenario.diagnostic),
|
|
});
|
|
const doctor = await fixture.run(doctorArguments);
|
|
expect(doctor.exitCode, doctor.stdout).toBe(1);
|
|
expect(doctor.json).toMatchObject({
|
|
healthy: false,
|
|
identity: {
|
|
registrations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
client: "gemini_cli",
|
|
state: scenario.state,
|
|
remediation: expect.stringContaining(scenario.diagnostic),
|
|
}),
|
|
]),
|
|
},
|
|
});
|
|
expect(await readFile(fixture.workspace)).toEqual(original);
|
|
expect(await readFile(fixture.user)).toEqual(fixture.originalUser);
|
|
await expect(
|
|
access(`${fixture.workspace}.rea.backup`),
|
|
).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
}
|
|
},
|
|
);
|