fix(contracts): align CLI omission and native capability coverage

This commit is contained in:
morluto committed 2026-10-26 22:22:27 +08:00
1 parent 4a8205456c
commit 2b84efe0da
8 files changed
+111 -44

No files matched your search

+1
View File
@@ -868,6 +868,7 @@ jobs:
tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts
tests/boundary/filesystem/setupUnavailableProvider.test.ts tests/boundary/filesystem/setupUnavailableProvider.test.ts
tests/boundary/filesystem/windowsNpxRegistration.test.ts tests/boundary/filesystem/windowsNpxRegistration.test.ts
tests/boundary/filesystem/claudeDesktopSkillUninstallWindows.test.ts
tests/boundary/filesystem/clientConfigurationEnvironment.test.ts tests/boundary/filesystem/clientConfigurationEnvironment.test.ts
tests/boundary/filesystem/clientConfigurationLayeredEnvironment.test.ts tests/boundary/filesystem/clientConfigurationLayeredEnvironment.test.ts
tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts
+1
View File
@@ -178,6 +178,7 @@ export const SCOPES = {
"tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts", "tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts",
"tests/boundary/filesystem/setupUnavailableProvider.test.ts", "tests/boundary/filesystem/setupUnavailableProvider.test.ts",
"tests/boundary/filesystem/windowsNpxRegistration.test.ts", "tests/boundary/filesystem/windowsNpxRegistration.test.ts",
"tests/boundary/filesystem/claudeDesktopSkillUninstallWindows.test.ts",
"tests/boundary/filesystem/clientConfigurationEnvironment.test.ts", "tests/boundary/filesystem/clientConfigurationEnvironment.test.ts",
"tests/boundary/filesystem/clientConfigurationLayeredEnvironment.test.ts", "tests/boundary/filesystem/clientConfigurationLayeredEnvironment.test.ts",
"tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts", "tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts",
+3 -2
View File
@@ -40,9 +40,10 @@ const withOmittableDefaults = (schema: z.ZodObject): z.ZodObject => {
); );
}; };
/** Zod accepts an omitted value (`optin`) but always produces one (`optout`). */ /** Let the object parser decide omission; Incur advertises required outputs. */
const fillsOmission = (field: z.ZodType): boolean => const fillsOmission = (field: z.ZodType): boolean =>
field._zod.optin === "optional" && field._zod.optout === undefined; field._zod.optout === undefined &&
z.object({ value: field }).safeParse({}).success;
const optionalWithMetadata = (field: z.ZodType): z.ZodType => { const optionalWithMetadata = (field: z.ZodType): z.ZodType => {
const metadata = field.meta(); const metadata = field.meta();
@@ -533,39 +533,6 @@ describe("MSIX Claude Desktop uninstall", () => {
expect(await readJson(appDataConfig)).toEqual(unrelated); expect(await readJson(appDataConfig)).toEqual(unrelated);
}); });
it("blocks managed skill removal through redirected AppData before any removal", async () => {
const { home, env, appDataConfig, privateConfig } = await windowsProfile();
await writeJson(join(dirname(privateConfig), "Preferences"), {});
await mkdir(dirname(dirname(appDataConfig)), { recursive: true });
await symlink(dirname(privateConfig), dirname(appDataConfig), "dir");
const agents = join(env.APPDATA, "dsh", "agents");
const manifest = join(
agents,
"skills",
"reverse-engineer-anything",
"SKILL.md",
);
await mkdir(dirname(manifest), { recursive: true });
await writeFile(
manifest,
"---\nname: reverse-engineer-anything\n---\nmanaged fixture\n",
);
const environment = { ...env, DSH_AGENTS_HOME: agents };
const result = await runUninstall(
false,
systemUninstallHost(home, undefined, environment, "win32"),
);
expect(result.status).toBe("failed");
expect(result.items).toContainEqual(
expect.objectContaining({
name: "skill",
status: "failed",
detail: expect.stringContaining("redirects its writes"),
}),
);
expect(await readFile(manifest, "utf8")).toContain("managed fixture");
});
it("removes REA from both the private copy and AppData on uninstall", async () => { it("removes REA from both the private copy and AppData on uninstall", async () => {
const { home, env, appDataConfig, privateConfig } = await windowsProfile(); const { home, env, appDataConfig, privateConfig } = await windowsProfile();
await writeJson(privateConfig, { await writeJson(privateConfig, {
@@ -0,0 +1,76 @@
import { access, mkdir, readFile, symlink, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { CLAUDE_DESKTOP_PACKAGE_FAMILY } from "../../../src/application/ClaudeDesktopConfigLocation.js";
import {
runUninstall,
systemUninstallHost,
} from "../../../src/application/Uninstall.js";
import { clearClientLocationEnvironment } from "../../fixtures/clientEnvironment.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
beforeEach(clearClientLocationEnvironment);
afterEach(() => vi.unstubAllEnvs());
// Explicit win32 skill destinations must be exercised on the native filesystem.
// A junction models the package's redirected AppData without symlink privilege.
it.runIf(process.platform === "win32").each([true, false])(
"preflights native managed skill removal with redirected AppData: %s",
async (redirected) => {
const home = await createTestTempDirectory("rea-claude-skill-uninstall-");
const env = {
APPDATA: join(home, "AppData", "Roaming"),
LOCALAPPDATA: join(home, "AppData", "Local"),
};
const privateClaude = join(
env.LOCALAPPDATA,
"Packages",
CLAUDE_DESKTOP_PACKAGE_FAMILY,
"LocalCache",
"Roaming",
"Claude",
);
await mkdir(privateClaude, { recursive: true });
await writeFile(join(privateClaude, "Preferences"), "{}");
await mkdir(env.APPDATA, { recursive: true });
if (redirected)
await symlink(privateClaude, join(env.APPDATA, "Claude"), "junction");
const agents = join(env.APPDATA, "dsh", "agents");
const manifests = [
join(agents, "skills", "reverse-engineer-anything", "SKILL.md"),
join(home, ".claude", "skills", "reverse-engineer-anything", "SKILL.md"),
];
const contents =
"---\nname: reverse-engineer-anything\n---\nmanaged fixture\n";
for (const manifest of manifests) {
await mkdir(dirname(manifest), { recursive: true });
await writeFile(manifest, contents);
}
const result = await runUninstall(
false,
systemUninstallHost(
home,
undefined,
{ ...env, DSH_AGENTS_HOME: agents },
"win32",
),
);
expect(result.status).toBe(redirected ? "failed" : "complete");
expect(result.items).toContainEqual(
expect.objectContaining({
name: "skill",
status: redirected ? "failed" : "removed",
...(redirected
? { detail: expect.stringContaining("redirects its writes") }
: {}),
}),
);
for (const manifest of manifests) {
if (redirected) expect(await readFile(manifest, "utf8")).toBe(contents);
else
await expect(access(manifest)).rejects.toMatchObject({
code: "ENOENT",
});
}
},
);
@@ -1,5 +1,5 @@
import { mkdir, readFile, writeFile } from "node:fs/promises"; import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path"; import { join, win32 } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -72,7 +72,12 @@ describe("Trae configuration paths", () => {
({ path }) => path, ({ path }) => path,
), ),
).toEqual([ ).toEqual([
join("C:\\Users\\a", ".trae", "skills", "reverse-engineer-anything"), win32.join(
"C:\\Users\\a",
".trae",
"skills",
"reverse-engineer-anything",
),
]); ]);
}); });
}); });
@@ -1,5 +1,5 @@
import { mkdir, readFile, writeFile } from "node:fs/promises"; import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path"; import { join, win32 } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -58,7 +58,12 @@ describe("WorkBuddy configuration paths", () => {
({ path }) => path, ({ path }) => path,
), ),
).toEqual([ ).toEqual([
join("C:\\Users\\a", ".workbuddy", "skills", "reverse-engineer-anything"), win32.join(
"C:\\Users\\a",
".workbuddy",
"skills",
"reverse-engineer-anything",
),
]); ]);
}); });
}); });
@@ -19,12 +19,17 @@ import { silentLogger } from "../../../src/logger.js";
import { SqliteDatabaseProvider } from "../../../src/sqlite/SqliteDatabaseProvider.js"; import { SqliteDatabaseProvider } from "../../../src/sqlite/SqliteDatabaseProvider.js";
import { PrivateRuntimeRoot } from "../../../src/process/PrivateRuntimeRoot.js"; import { PrivateRuntimeRoot } from "../../../src/process/PrivateRuntimeRoot.js";
import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js"; import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js";
import { createSqliteDatabaseFixture } from "../../fixtures/sqlite/database.js"; import {
createSqliteDatabaseFixture,
SQLITE_NATIVE_LIMITS_AVAILABLE,
} from "../../fixtures/sqlite/database.js";
import { import {
createTestWorkspace, createTestWorkspace,
removeTestWorkspace, removeTestWorkspace,
} from "../../support/workspace/workspaceFixture.js"; } from "../../support/workspace/workspaceFixture.js";
const nativeInspectionTest = it.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE);
const descriptorCleanup = vi.hoisted(() => ({ const descriptorCleanup = vi.hoisted(() => ({
path: "", path: "",
denied: false, denied: false,
@@ -351,7 +356,7 @@ it("retains reply-read cancellation and retries the failed reply descriptor clos
await expect(access(root.path)).rejects.toMatchObject({ code: "ENOENT" }); await expect(access(root.path)).rejects.toMatchObject({ code: "ENOENT" });
}); });
it.each([ nativeInspectionTest.each([
{ cancel: false, denied: false }, { cancel: false, denied: false },
{ cancel: true, denied: false }, { cancel: true, denied: false },
{ cancel: false, denied: true }, { cancel: false, denied: true },
@@ -480,7 +485,9 @@ it.each([
}, },
); );
it("keeps a completed reply close failure primary when abort follows lease retention", async () => { nativeInspectionTest(
"keeps a completed reply close failure primary when abort follows lease retention",
async () => {
const selected = await fixture("reply"); const selected = await fixture("reply");
const controller = new AbortController(); const controller = new AbortController();
const reason = { requested: "abort after reply close failure capture" }; const reason = { requested: "abort after reply close failure capture" };
@@ -490,7 +497,10 @@ it("keeps a completed reply close failure primary when abort follows lease reten
.spyOn(ArtifactResourceScope.prototype, "retain") .spyOn(ArtifactResourceScope.prototype, "retain")
.mockImplementation(function (this: ArtifactResourceScope, owner) { .mockImplementation(function (this: ArtifactResourceScope, owner) {
retain.call(this, owner); retain.call(this, owner);
if (owner.kind === "lease" && owner.resource === descriptorCleanup.path) { if (
owner.kind === "lease" &&
owner.resource === descriptorCleanup.path
) {
retainedReplyLease = owner.resource; retainedReplyLease = owner.resource;
controller.abort(reason); controller.abort(reason);
} }
@@ -537,7 +547,8 @@ it("keeps a completed reply close failure primary when abort follows lease reten
} finally { } finally {
retainSpy.mockRestore(); retainSpy.mockRestore();
} }
}); },
);
it("retries source cleanup before admitting another snapshot", async () => { it("retries source cleanup before admitting another snapshot", async () => {
const selected = await fixture("database"); const selected = await fixture("database");