@blackbelt-technology/pi-agent-dashboard 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +19 -30
- package/README.md +69 -1
- package/docs/architecture.md +89 -165
- package/package.json +11 -7
- package/packages/extension/package.json +2 -2
- package/packages/extension/src/__tests__/bridge-default-model-gate.test.ts +47 -0
- package/packages/extension/src/__tests__/bridge-followup-chat-order.test.ts +215 -0
- package/packages/extension/src/__tests__/bridge-followup-multi-entry.test.ts +202 -0
- package/packages/extension/src/__tests__/bridge-queue-update-forward.test.ts +77 -0
- package/packages/extension/src/__tests__/bridge-retry-ordering.test.ts +148 -0
- package/packages/extension/src/__tests__/bridge-shadow-queue-drain.test.ts +221 -0
- package/packages/extension/src/__tests__/bridge-shadow-queue-gate.test.ts +299 -0
- package/packages/extension/src/__tests__/bridge-shutdown-reset.test.ts +238 -0
- package/packages/extension/src/__tests__/bridge-slash-command-routing.test.ts +127 -31
- package/packages/extension/src/__tests__/command-handler.test.ts +105 -3
- package/packages/extension/src/__tests__/fixtures/usage-limit-error-strings.ts +127 -0
- package/packages/extension/src/__tests__/source-detector.test.ts +15 -0
- package/packages/extension/src/__tests__/usage-limit-orderer.test.ts +12 -0
- package/packages/extension/src/bridge-default-model-gate.ts +32 -0
- package/packages/extension/src/bridge.ts +299 -20
- package/packages/extension/src/command-handler.ts +53 -7
- package/packages/extension/src/dashboard-default-adapter.ts +5 -0
- package/packages/extension/src/prompt-bus.ts +15 -0
- package/packages/extension/src/slash-dispatch.ts +30 -15
- package/packages/extension/src/source-detector.ts +13 -5
- package/packages/extension/src/usage-limit-orderer.ts +18 -1
- package/packages/server/bin/pi-dashboard.mjs +62 -14
- package/packages/server/package.json +9 -5
- package/packages/server/src/__tests__/browser-gateway-register-handler.test.ts +69 -0
- package/packages/server/src/__tests__/cli-env-no-clobber.test.ts +46 -0
- package/packages/server/src/__tests__/cli-no-bootstrap-references.test.ts +69 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +9 -10
- package/packages/server/src/__tests__/cli-version.test.ts +151 -0
- package/packages/server/src/__tests__/directory-service-openspec-enabled.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service-toctou.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service.test.ts +9 -0
- package/packages/server/src/__tests__/doctor-route.test.ts +53 -0
- package/packages/server/src/__tests__/event-wiring-queue-state.test.ts +156 -0
- package/packages/server/src/__tests__/event-wiring-resume-clear.test.ts +105 -0
- package/packages/server/src/__tests__/health-shape.test.ts +35 -12
- package/packages/server/src/__tests__/installed-package-enricher.test.ts +12 -12
- package/packages/server/src/__tests__/is-activity-event.test.ts +4 -7
- package/packages/server/src/__tests__/package-routes.test.ts +6 -2
- package/packages/server/src/__tests__/pi-changelog-routes.test.ts +10 -13
- package/packages/server/src/__tests__/pi-core-checker.test.ts +2 -2
- package/packages/server/src/__tests__/pi-version-skew.test.ts +3 -2
- package/packages/server/src/__tests__/plugin-activation-routes.test.ts +267 -0
- package/packages/server/src/__tests__/plugin-intent-cache.test.ts +75 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +196 -0
- package/packages/server/src/__tests__/reattach-placement.test.ts +9 -0
- package/packages/server/src/__tests__/recommended-routes.test.ts +2 -2
- package/packages/server/src/__tests__/recovery-server.test.ts +203 -0
- package/packages/server/src/__tests__/session-action-handler-clear-queue.test.ts +153 -0
- package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +43 -0
- package/packages/server/src/__tests__/session-order-manager.test.ts +9 -0
- package/packages/server/src/__tests__/session-order-reboot.test.ts +9 -0
- package/packages/server/src/__tests__/session-ordering-integration.test.ts +9 -0
- package/packages/server/src/browser-gateway.ts +83 -5
- package/packages/server/src/browser-handlers/directory-handler.ts +69 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +89 -0
- package/packages/server/src/browser-handlers/subscription-handler.ts +23 -0
- package/packages/server/src/changelog-parser.ts +1 -1
- package/packages/server/src/cli.ts +68 -250
- package/packages/server/src/event-status-extraction.ts +14 -62
- package/packages/server/src/event-wiring.ts +23 -10
- package/packages/server/src/memory-session-manager.ts +4 -0
- package/packages/server/src/pi-core-checker.ts +1 -1
- package/packages/server/src/pi-dev-version-check.ts +1 -1
- package/packages/server/src/pi-version-skew.ts +24 -46
- package/packages/server/src/plugin-intent-cache.ts +67 -0
- package/packages/server/src/preferences-store.ts +199 -13
- package/packages/server/src/recovery-server.ts +366 -0
- package/packages/server/src/routes/__tests__/manifest-route.test.ts +138 -0
- package/packages/server/src/routes/doctor-routes.ts +26 -21
- package/packages/server/src/routes/manifest-route.ts +162 -0
- package/packages/server/src/routes/openspec-routes.ts +4 -25
- package/packages/server/src/routes/pi-changelog-routes.ts +5 -24
- package/packages/server/src/routes/pi-core-routes.ts +3 -23
- package/packages/server/src/routes/plugin-activation-routes.ts +193 -0
- package/packages/server/src/routes/recommended-routes.ts +21 -0
- package/packages/server/src/routes/system-routes.ts +73 -11
- package/packages/server/src/server.ts +105 -307
- package/packages/server/src/session-api.ts +5 -63
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/binary-lookup-resolveJiti.test.ts +28 -0
- package/packages/shared/src/__tests__/binary-lookup-spawn-env.test.ts +61 -0
- package/packages/shared/src/__tests__/binary-lookup.test.ts +16 -0
- package/packages/shared/src/__tests__/bridge-register.test.ts +67 -0
- package/packages/shared/src/__tests__/ci-electron-no-side-effects.test.ts +129 -0
- package/packages/shared/src/__tests__/config.test.ts +40 -0
- package/packages/shared/src/__tests__/dashboard-paths.test.ts +81 -0
- package/packages/shared/src/__tests__/ensure-windows-path.test.ts +112 -0
- package/packages/shared/src/__tests__/intent-types.test.ts +120 -0
- package/packages/shared/src/__tests__/jiti-packages-parity.test.ts +85 -0
- package/packages/shared/src/__tests__/legacy-managed-dir.test.ts +59 -0
- package/packages/shared/src/__tests__/no-direct-child-process.test.ts +12 -0
- package/packages/shared/src/__tests__/no-electron-execpath-spawn.test.ts +149 -0
- package/packages/shared/src/__tests__/no-flow-command-route-claims.test.ts +71 -0
- package/packages/shared/src/__tests__/no-flow-references-in-shell.test.ts +221 -0
- package/packages/shared/src/__tests__/no-managed-dir-reference.test.ts +134 -0
- package/packages/shared/src/__tests__/no-pi-dashboard-version-jiti-gate.test.ts +41 -0
- package/packages/shared/src/__tests__/no-primitive-direct-import.test.ts +235 -0
- package/packages/shared/src/__tests__/no-server-imports-in-resolver.test.ts +53 -0
- package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +54 -101
- package/packages/shared/src/__tests__/node-spawn.test.ts +29 -13
- package/packages/shared/src/__tests__/pi-package-resolver.test.ts +300 -0
- package/packages/shared/src/__tests__/plugin-activation-contracts.test.ts +74 -0
- package/packages/shared/src/__tests__/plugin-bridge-classify-source.test.ts +73 -0
- package/packages/shared/src/__tests__/plugin-bridge-register-extended.test.ts +17 -5
- package/packages/shared/src/__tests__/plugin-bridge-register-packages.test.ts +233 -0
- package/packages/shared/src/__tests__/plugin-bridge-register.test.ts +19 -9
- package/packages/shared/src/__tests__/publish-workflow-contract.test.ts +154 -15
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +28 -10
- package/packages/shared/src/__tests__/resolver-parity-with-scanner.test.ts +76 -0
- package/packages/shared/src/__tests__/server-identity.test.ts +127 -0
- package/packages/shared/src/__tests__/server-launcher.test.ts +35 -0
- package/packages/shared/src/__tests__/source-matching.test.ts +5 -5
- package/packages/shared/src/__tests__/sync-versions-spec.test.ts +76 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +50 -2
- package/packages/shared/src/bridge-register.ts +35 -2
- package/packages/shared/src/browser-protocol.ts +176 -2
- package/packages/shared/src/config.ts +12 -0
- package/packages/shared/src/dashboard-paths.ts +69 -0
- package/packages/shared/src/dashboard-plugin/index.ts +2 -0
- package/packages/shared/src/dashboard-plugin/intent-types.ts +93 -0
- package/packages/shared/src/dashboard-plugin/manifest-types.ts +55 -1
- package/packages/shared/src/dashboard-plugin/plugin-status.ts +82 -0
- package/packages/shared/src/dashboard-plugin/slot-props.ts +11 -0
- package/packages/shared/src/dashboard-plugin/slot-types.ts +16 -2
- package/packages/shared/src/dashboard-plugin/ui-primitives.ts +287 -0
- package/packages/shared/src/dashboard-starter.ts +22 -0
- package/packages/shared/src/doctor-core.ts +49 -27
- package/packages/shared/src/launch-source-types.ts +9 -9
- package/packages/shared/src/legacy-managed-dir.ts +97 -0
- package/packages/shared/src/mdns-discovery.ts +4 -1
- package/packages/shared/src/pi-package-resolver.ts +388 -0
- package/packages/shared/src/platform/binary-lookup.ts +27 -3
- package/packages/shared/src/platform/ensure-windows-path.ts +95 -0
- package/packages/shared/src/platform/exec.ts +22 -0
- package/packages/shared/src/platform/node-spawn.ts +42 -41
- package/packages/shared/src/plugin-bridge-register.ts +275 -18
- package/packages/shared/src/protocol.ts +94 -2
- package/packages/shared/src/recommended-extensions.ts +34 -10
- package/packages/shared/src/server-identity.ts +74 -5
- package/packages/shared/src/server-launcher.ts +20 -0
- package/packages/shared/src/source-matching.ts +1 -1
- package/packages/shared/src/tool-registry/__tests__/node-script-toargv-fallback.test.ts +84 -0
- package/packages/shared/src/tool-registry/definitions.ts +91 -7
- package/packages/shared/src/types.ts +12 -8
- package/scripts/maybe-patch-package.cjs +44 -0
- package/packages/server/src/__tests__/bootstrap-install-from-list.test.ts +0 -263
- package/packages/server/src/__tests__/bootstrap-queue.test.ts +0 -120
- package/packages/server/src/__tests__/bootstrap-routes.test.ts +0 -125
- package/packages/server/src/__tests__/bootstrap-state.test.ts +0 -119
- package/packages/server/src/__tests__/cli-bootstrap.test.ts +0 -36
- package/packages/server/src/__tests__/event-status-extraction-flow.test.ts +0 -55
- package/packages/server/src/__tests__/legacy-pi-cleanup.test.ts +0 -149
- package/packages/server/src/__tests__/post-install-openspec-refresh.test.ts +0 -180
- package/packages/server/src/__tests__/post-install-rescan.test.ts +0 -134
- package/packages/server/src/__tests__/system-routes-reextract.test.ts +0 -91
- package/packages/server/src/bootstrap-install-from-list.ts +0 -232
- package/packages/server/src/bootstrap-queue.ts +0 -130
- package/packages/server/src/bootstrap-state.ts +0 -159
- package/packages/server/src/legacy-pi-cleanup.ts +0 -151
- package/packages/server/src/routes/bootstrap-routes.ts +0 -125
- package/packages/shared/src/__tests__/bootstrap/README.md +0 -133
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +0 -378
- package/packages/shared/src/__tests__/bootstrap/assertions.ts +0 -136
- package/packages/shared/src/__tests__/bootstrap/cube.test.ts +0 -47
- package/packages/shared/src/__tests__/bootstrap/cube.ts +0 -66
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +0 -84
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +0 -90
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +0 -34
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +0 -20
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +0 -62
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +0 -34
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +0 -49
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +0 -12
- package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +0 -156
- package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +0 -157
- package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +0 -102
- package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +0 -76
- package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +0 -94
- package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +0 -87
- package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +0 -143
- package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +0 -64
- package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +0 -77
- package/packages/shared/src/__tests__/bootstrap/families/index.ts +0 -19
- package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +0 -61
- package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +0 -50
- package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +0 -272
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +0 -58
- package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +0 -84
- package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +0 -9
- package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +0 -85
- package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +0 -122
- package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +0 -36
- package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +0 -39
- package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +0 -220
- package/packages/shared/src/__tests__/bootstrap/harness.ts +0 -413
- package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +0 -125
- package/packages/shared/src/__tests__/bootstrap/scenarios.ts +0 -132
- package/packages/shared/src/__tests__/bootstrap-install-resolve-npm.test.ts +0 -72
- package/packages/shared/src/__tests__/install-managed-node-bootstrap-order.test.ts +0 -68
- package/packages/shared/src/__tests__/install-managed-node.test.ts +0 -192
- package/packages/shared/src/__tests__/installable-list.test.ts +0 -130
- package/packages/shared/src/__tests__/no-installable-list-in-bridge.test.ts +0 -52
- package/packages/shared/src/bootstrap-install.ts +0 -406
- package/packages/shared/src/installable-list.ts +0 -152
- package/packages/shared/src/launch-source-flag.ts +0 -14
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { extractSessionUpdates } from "../event-status-extraction.js";
|
|
3
|
-
import type { DashboardEvent } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
4
|
-
|
|
5
|
-
function makeEvent(eventType: string, data: Record<string, unknown> = {}): DashboardEvent {
|
|
6
|
-
return { eventType, timestamp: Date.now(), data };
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe("extractSessionUpdates flow events", () => {
|
|
10
|
-
it("flow_started extracts flow metadata", () => {
|
|
11
|
-
const updates = extractSessionUpdates(makeEvent("flow_started", {
|
|
12
|
-
flowName: "research-and-build",
|
|
13
|
-
steps: [
|
|
14
|
-
{ id: "r", stepType: "agent", agent: "researcher" },
|
|
15
|
-
{ id: "d", stepType: "agent", agent: "developer" },
|
|
16
|
-
{ id: "f1", stepType: "fork", question: "which?" },
|
|
17
|
-
],
|
|
18
|
-
}));
|
|
19
|
-
expect(updates).toEqual({
|
|
20
|
-
activeFlowName: "research-and-build",
|
|
21
|
-
flowAgentsTotal: 2,
|
|
22
|
-
flowAgentsDone: 0,
|
|
23
|
-
flowStatus: "running",
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("flow_agent_complete returns sentinel for increment", () => {
|
|
28
|
-
const updates = extractSessionUpdates(makeEvent("flow_agent_complete", {
|
|
29
|
-
agentName: "researcher",
|
|
30
|
-
result: { success: true },
|
|
31
|
-
}));
|
|
32
|
-
expect(updates).toEqual({ flowAgentsDone: -1 });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("flow_complete extracts status", () => {
|
|
36
|
-
const updates = extractSessionUpdates(makeEvent("flow_complete", {
|
|
37
|
-
status: "error",
|
|
38
|
-
flowName: "test",
|
|
39
|
-
}));
|
|
40
|
-
expect(updates).toEqual({ flowStatus: "error" });
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("flow_complete defaults to success", () => {
|
|
44
|
-
const updates = extractSessionUpdates(makeEvent("flow_complete", {
|
|
45
|
-
flowName: "test",
|
|
46
|
-
}));
|
|
47
|
-
expect(updates).toEqual({ flowStatus: "success" });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("other flow events return null", () => {
|
|
51
|
-
expect(extractSessionUpdates(makeEvent("flow_tool_call"))).toBeNull();
|
|
52
|
-
expect(extractSessionUpdates(makeEvent("flow_assistant_text"))).toBeNull();
|
|
53
|
-
expect(extractSessionUpdates(makeEvent("flow_loop_iteration"))).toBeNull();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import {
|
|
6
|
-
parseVersion,
|
|
7
|
-
legacyPathUnder,
|
|
8
|
-
detectLegacyPiInstalls,
|
|
9
|
-
uninstallLegacyPi,
|
|
10
|
-
LEGACY_PI_PACKAGE,
|
|
11
|
-
type LegacyPiInstall,
|
|
12
|
-
} from "../legacy-pi-cleanup.js";
|
|
13
|
-
|
|
14
|
-
describe("parseVersion", () => {
|
|
15
|
-
it("returns version from valid json", () => {
|
|
16
|
-
expect(parseVersion('{"name":"x","version":"1.2.3"}')).toBe("1.2.3");
|
|
17
|
-
});
|
|
18
|
-
it("returns null on parse error", () => {
|
|
19
|
-
expect(parseVersion("not json")).toBeNull();
|
|
20
|
-
});
|
|
21
|
-
it("returns null when version missing", () => {
|
|
22
|
-
expect(parseVersion('{"name":"x"}')).toBeNull();
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe("legacyPathUnder", () => {
|
|
27
|
-
it("joins node_modules with legacy package", () => {
|
|
28
|
-
const p = legacyPathUnder("/tmp/nm");
|
|
29
|
-
expect(p.endsWith(path.join("@mariozechner", "pi-coding-agent"))).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe("detectLegacyPiInstalls (filesystem)", () => {
|
|
34
|
-
let tmpHome: string;
|
|
35
|
-
let origHome: string | undefined;
|
|
36
|
-
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "legacy-pi-test-"));
|
|
39
|
-
origHome = process.env.HOME;
|
|
40
|
-
process.env.HOME = tmpHome;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
afterEach(() => {
|
|
44
|
-
if (origHome !== undefined) process.env.HOME = origHome;
|
|
45
|
-
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
function plantLegacy(scopeDir: string, version: string): string {
|
|
49
|
-
const pkgDir = path.join(scopeDir, ...LEGACY_PI_PACKAGE.split("/"));
|
|
50
|
-
fs.mkdirSync(pkgDir, { recursive: true });
|
|
51
|
-
fs.writeFileSync(
|
|
52
|
-
path.join(pkgDir, "package.json"),
|
|
53
|
-
JSON.stringify({ name: LEGACY_PI_PACKAGE, version }),
|
|
54
|
-
);
|
|
55
|
-
return pkgDir;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
it("returns empty when nothing planted", () => {
|
|
59
|
-
// Note: this still consults `npm root -g` from the real system; if that
|
|
60
|
-
// happens to contain @mariozechner/pi-coding-agent the test would
|
|
61
|
-
// see it. We accept that and only assert npx-cache + managed are empty.
|
|
62
|
-
const found = detectLegacyPiInstalls();
|
|
63
|
-
expect(found.filter((f) => f.scope !== "npm-global")).toEqual([]);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("detects npx-cache install", () => {
|
|
67
|
-
plantLegacy(path.join(tmpHome, ".npm", "_npx", "abc123", "node_modules"), "0.73.1");
|
|
68
|
-
const found = detectLegacyPiInstalls().filter((f) => f.scope === "npx-cache");
|
|
69
|
-
expect(found).toHaveLength(1);
|
|
70
|
-
expect(found[0].version).toBe("0.73.1");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("detects managed install", () => {
|
|
74
|
-
plantLegacy(path.join(tmpHome, ".pi-dashboard", "node_modules"), "0.70.0");
|
|
75
|
-
const found = detectLegacyPiInstalls().filter((f) => f.scope === "managed");
|
|
76
|
-
expect(found).toHaveLength(1);
|
|
77
|
-
expect(found[0].version).toBe("0.70.0");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("detects multiple npx-cache installs", () => {
|
|
81
|
-
plantLegacy(path.join(tmpHome, ".npm", "_npx", "h1", "node_modules"), "0.72.0");
|
|
82
|
-
plantLegacy(path.join(tmpHome, ".npm", "_npx", "h2", "node_modules"), "0.73.0");
|
|
83
|
-
const found = detectLegacyPiInstalls().filter((f) => f.scope === "npx-cache");
|
|
84
|
-
expect(found).toHaveLength(2);
|
|
85
|
-
expect(found.map((f) => f.version).sort()).toEqual(["0.72.0", "0.73.0"]);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe("uninstallLegacyPi (filesystem subset)", () => {
|
|
90
|
-
let tmpHome: string;
|
|
91
|
-
let origHome: string | undefined;
|
|
92
|
-
|
|
93
|
-
beforeEach(() => {
|
|
94
|
-
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "legacy-pi-rm-test-"));
|
|
95
|
-
origHome = process.env.HOME;
|
|
96
|
-
process.env.HOME = tmpHome;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
afterEach(() => {
|
|
100
|
-
if (origHome !== undefined) process.env.HOME = origHome;
|
|
101
|
-
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
function plant(scope: "managed" | "npx-cache", version: string): LegacyPiInstall {
|
|
105
|
-
const base =
|
|
106
|
-
scope === "managed"
|
|
107
|
-
? path.join(tmpHome, ".pi-dashboard", "node_modules")
|
|
108
|
-
: path.join(tmpHome, ".npm", "_npx", "x1", "node_modules");
|
|
109
|
-
const pkgDir = path.join(base, ...LEGACY_PI_PACKAGE.split("/"));
|
|
110
|
-
fs.mkdirSync(pkgDir, { recursive: true });
|
|
111
|
-
fs.writeFileSync(path.join(pkgDir, "package.json"), JSON.stringify({ version }));
|
|
112
|
-
return { scope, path: pkgDir, version };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
it("removes managed install via rm -rf", () => {
|
|
116
|
-
const install = plant("managed", "0.70.0");
|
|
117
|
-
expect(fs.existsSync(install.path)).toBe(true);
|
|
118
|
-
const results = uninstallLegacyPi([install]);
|
|
119
|
-
expect(results[0]).toEqual({ scope: "managed", path: install.path, removed: true });
|
|
120
|
-
expect(fs.existsSync(install.path)).toBe(false);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("removes npx-cache install via rm -rf", () => {
|
|
124
|
-
const install = plant("npx-cache", "0.73.1");
|
|
125
|
-
const results = uninstallLegacyPi([install]);
|
|
126
|
-
expect(results[0].removed).toBe(true);
|
|
127
|
-
expect(fs.existsSync(install.path)).toBe(false);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("returns error result when path does not exist (rm force suppresses, returns removed=true)", () => {
|
|
131
|
-
// fs.rmSync with force:true treats missing paths as success.
|
|
132
|
-
const install: LegacyPiInstall = {
|
|
133
|
-
scope: "managed",
|
|
134
|
-
path: path.join(tmpHome, "does-not-exist"),
|
|
135
|
-
version: null,
|
|
136
|
-
};
|
|
137
|
-
const results = uninstallLegacyPi([install]);
|
|
138
|
-
expect(results[0].removed).toBe(true);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("processes multiple installs independently", () => {
|
|
142
|
-
const a = plant("managed", "0.70.0");
|
|
143
|
-
const b = plant("npx-cache", "0.73.0");
|
|
144
|
-
const results = uninstallLegacyPi([a, b]);
|
|
145
|
-
expect(results.every((r) => r.removed)).toBe(true);
|
|
146
|
-
expect(fs.existsSync(a.path)).toBe(false);
|
|
147
|
-
expect(fs.existsSync(b.path)).toBe(false);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the post-install OpenSpec + pi-resources force-refresh
|
|
3
|
-
* portion of `runPostInstallRepair`.
|
|
4
|
-
*
|
|
5
|
-
* See change: fix-openspec-buttons-after-bootstrap-install.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
8
|
-
import { runPostInstallRepair } from "../server.js";
|
|
9
|
-
|
|
10
|
-
interface SpyRegistry { rescan: ReturnType<typeof vi.fn>; }
|
|
11
|
-
interface SpyDirSvc {
|
|
12
|
-
knownDirectories: ReturnType<typeof vi.fn>;
|
|
13
|
-
getOpenSpecData: ReturnType<typeof vi.fn>;
|
|
14
|
-
refreshOpenSpec: ReturnType<typeof vi.fn>;
|
|
15
|
-
refreshPiResources: ReturnType<typeof vi.fn>;
|
|
16
|
-
}
|
|
17
|
-
interface SpyGateway { broadcastToAll: ReturnType<typeof vi.fn>; }
|
|
18
|
-
|
|
19
|
-
function makeRegistry(): SpyRegistry { return { rescan: vi.fn() }; }
|
|
20
|
-
function makeGateway(): SpyGateway { return { broadcastToAll: vi.fn() }; }
|
|
21
|
-
|
|
22
|
-
describe("runPostInstallRepair: openspec + pi-resources refresh", () => {
|
|
23
|
-
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
24
|
-
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("calls refreshOpenSpec(cwd) once for every known directory", async () => {
|
|
30
|
-
const registry = makeRegistry();
|
|
31
|
-
const cwds = ["/p1", "/p2", "/p3"];
|
|
32
|
-
const directoryService: SpyDirSvc = {
|
|
33
|
-
knownDirectories: vi.fn(() => cwds),
|
|
34
|
-
getOpenSpecData: vi.fn(() => ({ initialized: false, changes: [] })),
|
|
35
|
-
refreshOpenSpec: vi.fn(async () => ({ initialized: true, changes: [{ name: "x" } as never] })),
|
|
36
|
-
refreshPiResources: vi.fn(async () => ({})),
|
|
37
|
-
};
|
|
38
|
-
const browserGateway = makeGateway();
|
|
39
|
-
|
|
40
|
-
await runPostInstallRepair({
|
|
41
|
-
registry: registry as never,
|
|
42
|
-
directoryService: directoryService as never,
|
|
43
|
-
browserGateway: browserGateway as never,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
expect(directoryService.refreshOpenSpec).toHaveBeenCalledTimes(3);
|
|
47
|
-
expect(directoryService.refreshOpenSpec).toHaveBeenNthCalledWith(1, "/p1");
|
|
48
|
-
expect(directoryService.refreshOpenSpec).toHaveBeenNthCalledWith(2, "/p2");
|
|
49
|
-
expect(directoryService.refreshOpenSpec).toHaveBeenNthCalledWith(3, "/p3");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("broadcasts openspec_update for each cwd whose prior cache was empty/undefined", async () => {
|
|
53
|
-
const cwds = ["/a", "/b"];
|
|
54
|
-
const fresh = { initialized: true, changes: [{ name: "c1" } as never] };
|
|
55
|
-
const directoryService: SpyDirSvc = {
|
|
56
|
-
knownDirectories: vi.fn(() => cwds),
|
|
57
|
-
// Prior was undefined for /a, empty for /b — both should broadcast.
|
|
58
|
-
getOpenSpecData: vi.fn((cwd: string) =>
|
|
59
|
-
cwd === "/a" ? undefined : { initialized: false, changes: [] }
|
|
60
|
-
),
|
|
61
|
-
refreshOpenSpec: vi.fn(async () => fresh),
|
|
62
|
-
refreshPiResources: vi.fn(async () => ({})),
|
|
63
|
-
};
|
|
64
|
-
const browserGateway = makeGateway();
|
|
65
|
-
|
|
66
|
-
await runPostInstallRepair({
|
|
67
|
-
registry: makeRegistry() as never,
|
|
68
|
-
directoryService: directoryService as never,
|
|
69
|
-
browserGateway: browserGateway as never,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const broadcasts = browserGateway.broadcastToAll.mock.calls
|
|
73
|
-
.map((c: unknown[]) => c[0])
|
|
74
|
-
.filter((m: any) => m?.type === "openspec_update");
|
|
75
|
-
expect(broadcasts).toHaveLength(2);
|
|
76
|
-
expect(broadcasts).toContainEqual({ type: "openspec_update", cwd: "/a", data: fresh });
|
|
77
|
-
expect(broadcasts).toContainEqual({ type: "openspec_update", cwd: "/b", data: fresh });
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("does not broadcast openspec_update when refreshed data equals prior data", async () => {
|
|
81
|
-
const same = { initialized: true, changes: [{ name: "stable" } as never] };
|
|
82
|
-
const directoryService: SpyDirSvc = {
|
|
83
|
-
knownDirectories: vi.fn(() => ["/p"]),
|
|
84
|
-
getOpenSpecData: vi.fn(() => same),
|
|
85
|
-
refreshOpenSpec: vi.fn(async () => same),
|
|
86
|
-
refreshPiResources: vi.fn(async () => ({})),
|
|
87
|
-
};
|
|
88
|
-
const browserGateway = makeGateway();
|
|
89
|
-
|
|
90
|
-
await runPostInstallRepair({
|
|
91
|
-
registry: makeRegistry() as never,
|
|
92
|
-
directoryService: directoryService as never,
|
|
93
|
-
browserGateway: browserGateway as never,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const broadcasts = browserGateway.broadcastToAll.mock.calls
|
|
97
|
-
.map((c: unknown[]) => c[0])
|
|
98
|
-
.filter((m: any) => m?.type === "openspec_update");
|
|
99
|
-
expect(broadcasts).toHaveLength(0);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("isolates a per-cwd refresh failure — the other cwd still refreshes and broadcasts", async () => {
|
|
103
|
-
const cwds = ["/good", "/bad"];
|
|
104
|
-
const fresh = { initialized: true, changes: [{ name: "ok" } as never] };
|
|
105
|
-
const directoryService: SpyDirSvc = {
|
|
106
|
-
knownDirectories: vi.fn(() => cwds),
|
|
107
|
-
getOpenSpecData: vi.fn(() => undefined),
|
|
108
|
-
refreshOpenSpec: vi.fn(async (cwd: string) => {
|
|
109
|
-
if (cwd === "/bad") throw new Error("boom");
|
|
110
|
-
return fresh;
|
|
111
|
-
}),
|
|
112
|
-
refreshPiResources: vi.fn(async () => ({})),
|
|
113
|
-
};
|
|
114
|
-
const browserGateway = makeGateway();
|
|
115
|
-
|
|
116
|
-
await runPostInstallRepair({
|
|
117
|
-
registry: makeRegistry() as never,
|
|
118
|
-
directoryService: directoryService as never,
|
|
119
|
-
browserGateway: browserGateway as never,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Both cwds attempted.
|
|
123
|
-
expect(directoryService.refreshOpenSpec).toHaveBeenCalledTimes(2);
|
|
124
|
-
// Only the good one broadcasts.
|
|
125
|
-
const broadcasts = browserGateway.broadcastToAll.mock.calls
|
|
126
|
-
.map((c: unknown[]) => c[0])
|
|
127
|
-
.filter((m: any) => m?.type === "openspec_update" && m.cwd === "/good");
|
|
128
|
-
expect(broadcasts).toHaveLength(1);
|
|
129
|
-
// Failure was logged, not propagated.
|
|
130
|
-
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("calls refreshPiResources(cwd) once for every known directory", async () => {
|
|
134
|
-
const cwds = ["/a", "/b", "/c"];
|
|
135
|
-
const directoryService: SpyDirSvc = {
|
|
136
|
-
knownDirectories: vi.fn(() => cwds),
|
|
137
|
-
getOpenSpecData: vi.fn(() => undefined),
|
|
138
|
-
refreshOpenSpec: vi.fn(async () => ({ initialized: false, changes: [] })),
|
|
139
|
-
refreshPiResources: vi.fn(async () => ({})),
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
await runPostInstallRepair({
|
|
143
|
-
registry: makeRegistry() as never,
|
|
144
|
-
directoryService: directoryService as never,
|
|
145
|
-
browserGateway: makeGateway() as never,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
expect(directoryService.refreshPiResources).toHaveBeenCalledTimes(3);
|
|
149
|
-
expect(directoryService.refreshPiResources).toHaveBeenCalledWith("/a");
|
|
150
|
-
expect(directoryService.refreshPiResources).toHaveBeenCalledWith("/b");
|
|
151
|
-
expect(directoryService.refreshPiResources).toHaveBeenCalledWith("/c");
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it("a refreshPiResources failure does not block other cwds or openspec broadcasts", async () => {
|
|
155
|
-
const cwds = ["/good", "/bad"];
|
|
156
|
-
const fresh = { initialized: true, changes: [{ name: "ok" } as never] };
|
|
157
|
-
const directoryService: SpyDirSvc = {
|
|
158
|
-
knownDirectories: vi.fn(() => cwds),
|
|
159
|
-
getOpenSpecData: vi.fn(() => undefined),
|
|
160
|
-
refreshOpenSpec: vi.fn(async () => fresh),
|
|
161
|
-
refreshPiResources: vi.fn(async (cwd: string) => {
|
|
162
|
-
if (cwd === "/bad") throw new Error("pi-resources blew up");
|
|
163
|
-
return {};
|
|
164
|
-
}),
|
|
165
|
-
};
|
|
166
|
-
const browserGateway = makeGateway();
|
|
167
|
-
|
|
168
|
-
await runPostInstallRepair({
|
|
169
|
-
registry: makeRegistry() as never,
|
|
170
|
-
directoryService: directoryService as never,
|
|
171
|
-
browserGateway: browserGateway as never,
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
expect(directoryService.refreshPiResources).toHaveBeenCalledTimes(2);
|
|
175
|
-
const openSpecBroadcasts = browserGateway.broadcastToAll.mock.calls
|
|
176
|
-
.map((c: unknown[]) => c[0])
|
|
177
|
-
.filter((m: any) => m?.type === "openspec_update");
|
|
178
|
-
expect(openSpecBroadcasts).toHaveLength(2);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the centralized post-install rescan hook.
|
|
3
|
-
*
|
|
4
|
-
* Asserts that the helper invoked from the bootstrap-state subscribe
|
|
5
|
-
* callback calls `registry.rescan()` (no arg → full registry invalidate)
|
|
6
|
-
* exactly once on `installing → ready` and never on other transitions.
|
|
7
|
-
*
|
|
8
|
-
* See change: fix-openspec-buttons-after-bootstrap-install.
|
|
9
|
-
*/
|
|
10
|
-
import { describe, it, expect, vi } from "vitest";
|
|
11
|
-
import { runPostInstallRepair, makeBootstrapTransitionHandler } from "../server.js";
|
|
12
|
-
|
|
13
|
-
interface SpyRegistry {
|
|
14
|
-
rescan: ReturnType<typeof vi.fn>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface SpyDirSvc {
|
|
18
|
-
knownDirectories: ReturnType<typeof vi.fn>;
|
|
19
|
-
getOpenSpecData: ReturnType<typeof vi.fn>;
|
|
20
|
-
refreshOpenSpec: ReturnType<typeof vi.fn>;
|
|
21
|
-
refreshPiResources: ReturnType<typeof vi.fn>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface SpyGateway {
|
|
25
|
-
broadcastToAll: ReturnType<typeof vi.fn>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function makeRegistry(): SpyRegistry {
|
|
29
|
-
return { rescan: vi.fn() };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function makeDirSvc(): SpyDirSvc {
|
|
33
|
-
return {
|
|
34
|
-
knownDirectories: vi.fn(() => []),
|
|
35
|
-
getOpenSpecData: vi.fn(() => undefined),
|
|
36
|
-
refreshOpenSpec: vi.fn(async () => ({ initialized: false, changes: [] })),
|
|
37
|
-
refreshPiResources: vi.fn(async () => ({ initialized: false, packages: [] })),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function makeGateway(): SpyGateway {
|
|
42
|
-
return { broadcastToAll: vi.fn() };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
describe("runPostInstallRepair", () => {
|
|
46
|
-
it("calls registry.rescan() with no argument (full registry)", async () => {
|
|
47
|
-
const registry = makeRegistry();
|
|
48
|
-
const directoryService = makeDirSvc();
|
|
49
|
-
const browserGateway = makeGateway();
|
|
50
|
-
await runPostInstallRepair({
|
|
51
|
-
registry: registry as never,
|
|
52
|
-
directoryService: directoryService as never,
|
|
53
|
-
browserGateway: browserGateway as never,
|
|
54
|
-
});
|
|
55
|
-
expect(registry.rescan).toHaveBeenCalledTimes(1);
|
|
56
|
-
expect(registry.rescan).toHaveBeenCalledWith();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe("makeBootstrapTransitionHandler", () => {
|
|
61
|
-
it("invokes the post-install repair exactly once on installing → ready", async () => {
|
|
62
|
-
const repair = vi.fn(async () => undefined);
|
|
63
|
-
const flushAll = vi.fn(async () => undefined);
|
|
64
|
-
const handler = makeBootstrapTransitionHandler({
|
|
65
|
-
onTransitionToReady: repair,
|
|
66
|
-
flushQueue: flushAll,
|
|
67
|
-
});
|
|
68
|
-
handler({ status: "installing" } as never);
|
|
69
|
-
handler({ status: "ready" } as never);
|
|
70
|
-
// Wait one microtask tick for the fire-and-forget to fire.
|
|
71
|
-
await Promise.resolve();
|
|
72
|
-
expect(repair).toHaveBeenCalledTimes(1);
|
|
73
|
-
expect(flushAll).toHaveBeenCalledTimes(1);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("does not call repair on ready → ready", async () => {
|
|
77
|
-
const repair = vi.fn(async () => undefined);
|
|
78
|
-
const flushAll = vi.fn(async () => undefined);
|
|
79
|
-
const handler = makeBootstrapTransitionHandler({
|
|
80
|
-
onTransitionToReady: repair,
|
|
81
|
-
flushQueue: flushAll,
|
|
82
|
-
});
|
|
83
|
-
// Initial state defaults to ready, so first ready-snapshot is a no-op.
|
|
84
|
-
handler({ status: "ready" } as never);
|
|
85
|
-
handler({ status: "ready" } as never);
|
|
86
|
-
await Promise.resolve();
|
|
87
|
-
expect(repair).not.toHaveBeenCalled();
|
|
88
|
-
expect(flushAll).not.toHaveBeenCalled();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("does not call repair on installing → failed", async () => {
|
|
92
|
-
const repair = vi.fn(async () => undefined);
|
|
93
|
-
const flushAll = vi.fn(async () => undefined);
|
|
94
|
-
const handler = makeBootstrapTransitionHandler({
|
|
95
|
-
onTransitionToReady: repair,
|
|
96
|
-
flushQueue: flushAll,
|
|
97
|
-
});
|
|
98
|
-
handler({ status: "installing" } as never);
|
|
99
|
-
handler({ status: "failed" } as never);
|
|
100
|
-
await Promise.resolve();
|
|
101
|
-
expect(repair).not.toHaveBeenCalled();
|
|
102
|
-
expect(flushAll).not.toHaveBeenCalled();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("does not call repair on the very first subscribe snapshot", async () => {
|
|
106
|
-
// Bootstrap state defaults to "ready"; the first emitted snapshot
|
|
107
|
-
// (e.g. from an immediate broadcast) should NOT trigger the hook.
|
|
108
|
-
const repair = vi.fn(async () => undefined);
|
|
109
|
-
const flushAll = vi.fn(async () => undefined);
|
|
110
|
-
const handler = makeBootstrapTransitionHandler({
|
|
111
|
-
onTransitionToReady: repair,
|
|
112
|
-
flushQueue: flushAll,
|
|
113
|
-
});
|
|
114
|
-
handler({ status: "ready" } as never);
|
|
115
|
-
await Promise.resolve();
|
|
116
|
-
expect(repair).not.toHaveBeenCalled();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("calls repair on a second installing → ready cycle (e.g. user retry)", async () => {
|
|
120
|
-
const repair = vi.fn(async () => undefined);
|
|
121
|
-
const flushAll = vi.fn(async () => undefined);
|
|
122
|
-
const handler = makeBootstrapTransitionHandler({
|
|
123
|
-
onTransitionToReady: repair,
|
|
124
|
-
flushQueue: flushAll,
|
|
125
|
-
});
|
|
126
|
-
handler({ status: "installing" } as never);
|
|
127
|
-
handler({ status: "ready" } as never);
|
|
128
|
-
handler({ status: "installing" } as never);
|
|
129
|
-
handler({ status: "ready" } as never);
|
|
130
|
-
await Promise.resolve();
|
|
131
|
-
expect(repair).toHaveBeenCalledTimes(2);
|
|
132
|
-
expect(flushAll).toHaveBeenCalledTimes(2);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for POST /api/electron/reextract
|
|
3
|
-
* See change: simplify-electron-bootstrap-derived-state (task 6.4 / 6.9).
|
|
4
|
-
*/
|
|
5
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
6
|
-
import Fastify, { type FastifyInstance } from "fastify";
|
|
7
|
-
import { registerSystemRoutes } from "../routes/system-routes.js";
|
|
8
|
-
import type { BootstrapStateStore, BootstrapState } from "../bootstrap-state.js";
|
|
9
|
-
|
|
10
|
-
function noGuard() {
|
|
11
|
-
return async () => { /* allow all */ };
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function makeBootstrapState(starter: string): BootstrapStateStore {
|
|
15
|
-
return {
|
|
16
|
-
get: () => ({
|
|
17
|
-
status: "ready",
|
|
18
|
-
starter: starter as any,
|
|
19
|
-
installable: { total: 0, installed: 0, failed: [] },
|
|
20
|
-
} as BootstrapState),
|
|
21
|
-
set: () => {},
|
|
22
|
-
subscribe: () => () => {},
|
|
23
|
-
} as unknown as BootstrapStateStore;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function makeNoopDeps(bootstrapState?: BootstrapStateStore) {
|
|
27
|
-
return {
|
|
28
|
-
sessionManager: { listActive: () => [], listAll: () => [] } as never,
|
|
29
|
-
preferencesStore: { flush: () => {} } as never,
|
|
30
|
-
metaPersistence: { flushAll: () => {} } as never,
|
|
31
|
-
config: { port: 8000, piPort: 9999, dev: false } as never,
|
|
32
|
-
networkGuard: noGuard(),
|
|
33
|
-
bootstrapState,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
describe("POST /api/electron/reextract", () => {
|
|
38
|
-
let fastify: FastifyInstance;
|
|
39
|
-
|
|
40
|
-
beforeEach(async () => {
|
|
41
|
-
fastify = Fastify();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
afterEach(async () => {
|
|
45
|
-
await fastify.close();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("returns 403 when starter is Bridge", async () => {
|
|
49
|
-
const deps = makeNoopDeps(makeBootstrapState("Bridge"));
|
|
50
|
-
registerSystemRoutes(fastify, deps);
|
|
51
|
-
await fastify.ready();
|
|
52
|
-
|
|
53
|
-
const res = await fastify.inject({ method: "POST", url: "/api/electron/reextract" });
|
|
54
|
-
expect(res.statusCode).toBe(403);
|
|
55
|
-
const body = res.json() as Record<string, unknown>;
|
|
56
|
-
expect(body.error).toBe("reextract_not_allowed");
|
|
57
|
-
expect(body.starter).toBe("Bridge");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("returns 403 when starter is Standalone", async () => {
|
|
61
|
-
const deps = makeNoopDeps(makeBootstrapState("Standalone"));
|
|
62
|
-
registerSystemRoutes(fastify, deps);
|
|
63
|
-
await fastify.ready();
|
|
64
|
-
|
|
65
|
-
const res = await fastify.inject({ method: "POST", url: "/api/electron/reextract" });
|
|
66
|
-
expect(res.statusCode).toBe(403);
|
|
67
|
-
const body = res.json() as Record<string, unknown>;
|
|
68
|
-
expect(body.error).toBe("reextract_not_allowed");
|
|
69
|
-
expect(body.starter).toBe("Standalone");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("returns 202 when starter is Electron", async () => {
|
|
73
|
-
const deps = makeNoopDeps(makeBootstrapState("Electron"));
|
|
74
|
-
registerSystemRoutes(fastify, deps);
|
|
75
|
-
await fastify.ready();
|
|
76
|
-
|
|
77
|
-
const res = await fastify.inject({ method: "POST", url: "/api/electron/reextract" });
|
|
78
|
-
expect(res.statusCode).toBe(202);
|
|
79
|
-
const body = res.json() as Record<string, unknown>;
|
|
80
|
-
expect(body.ok).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("returns 403 when no bootstrapState (defaults to Standalone)", async () => {
|
|
84
|
-
const deps = makeNoopDeps(undefined);
|
|
85
|
-
registerSystemRoutes(fastify, deps);
|
|
86
|
-
await fastify.ready();
|
|
87
|
-
|
|
88
|
-
const res = await fastify.inject({ method: "POST", url: "/api/electron/reextract" });
|
|
89
|
-
expect(res.statusCode).toBe(403);
|
|
90
|
-
});
|
|
91
|
-
});
|