mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(contracts): align CLI omission and native capability coverage
This commit is contained in:
1 parent
4a8205456c
commit
2b84efe0da
8 files changed
+111
-44
No files matched your search
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in new issue
Block a user