




















1+// Verifies request-scoped plugin workspace pins under concurrent registry mutation.
2+import { afterEach, describe, expect, it } from "vitest";
3+import { createEmptyPluginRegistry } from "./registry-empty.js";
4+import { getActivePluginRegistryWorkspaceDirFromState } from "./runtime-state.js";
5+import { withPinnedActivePluginRegistryWorkspaceDir } from "./runtime-workspace-state.js";
6+import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "./runtime.js";
7+8+function setActiveWorkspace(workspaceDir: string): void {
9+setActivePluginRegistry(
10+createEmptyPluginRegistry(),
11+workspaceDir,
12+"gateway-bindable",
13+workspaceDir,
14+);
15+}
16+17+function createDeferred(): { promise: Promise<void>; resolve: () => void } {
18+let resolve: (() => void) | undefined;
19+const promise = new Promise<void>((done) => {
20+resolve = done;
21+});
22+if (!resolve) {
23+throw new Error("Expected deferred resolver");
24+}
25+return { promise, resolve };
26+}
27+28+describe("runtime workspace state pin", () => {
29+afterEach(() => {
30+resetPluginRuntimeStateForTest();
31+});
32+33+it("reads the live global workspace dir when no pin is active", () => {
34+setActiveWorkspace("/workspace/a");
35+expect(getActivePluginRegistryWorkspaceDirFromState()).toBe("/workspace/a");
36+37+setActiveWorkspace("/workspace/b");
38+expect(getActivePluginRegistryWorkspaceDirFromState()).toBe("/workspace/b");
39+});
40+41+it("isolates overlapping pins and restores live state after success", async () => {
42+const firstPinned = createDeferred();
43+const resumeFirst = createDeferred();
44+const firstObserved: Array<string | undefined> = [];
45+46+setActiveWorkspace("/workspace/a");
47+const firstRequest = withPinnedActivePluginRegistryWorkspaceDir(async () => {
48+firstObserved.push(getActivePluginRegistryWorkspaceDirFromState());
49+firstPinned.resolve();
50+await resumeFirst.promise;
51+firstObserved.push(getActivePluginRegistryWorkspaceDirFromState());
52+});
53+await firstPinned.promise;
54+55+setActiveWorkspace("/workspace/b");
56+const secondObserved = await withPinnedActivePluginRegistryWorkspaceDir(async () => {
57+const observed = [getActivePluginRegistryWorkspaceDirFromState()];
58+setActiveWorkspace("/workspace/c");
59+await new Promise<void>((resolve) => {
60+setImmediate(resolve);
61+});
62+observed.push(getActivePluginRegistryWorkspaceDirFromState());
63+return observed;
64+});
65+66+resumeFirst.resolve();
67+await firstRequest;
68+69+expect(firstObserved).toEqual(["/workspace/a", "/workspace/a"]);
70+expect(secondObserved).toEqual(["/workspace/b", "/workspace/b"]);
71+expect(getActivePluginRegistryWorkspaceDirFromState()).toBe("/workspace/c");
72+});
73+74+it("reuses the outer pin for nested scopes", async () => {
75+setActiveWorkspace("/workspace/a");
76+77+await withPinnedActivePluginRegistryWorkspaceDir(async () => {
78+setActiveWorkspace("/workspace/b");
79+await withPinnedActivePluginRegistryWorkspaceDir(async () => {
80+expect(getActivePluginRegistryWorkspaceDirFromState()).toBe("/workspace/a");
81+});
82+});
83+84+expect(getActivePluginRegistryWorkspaceDirFromState()).toBe("/workspace/b");
85+});
86+87+it("preserves the pin across module reloads", async () => {
88+setActiveWorkspace("/workspace/a");
89+90+await withPinnedActivePluginRegistryWorkspaceDir(async () => {
91+setActiveWorkspace("/workspace/b");
92+const reloaded = await import(
93+new URL("./runtime-workspace-state.ts?workspace-pin-reload", import.meta.url).href
94+);
95+96+expect(reloaded.getActivePluginRegistryWorkspaceDirFromState()).toBe("/workspace/a");
97+});
98+});
99+100+it("propagates rejections without leaking the pin", async () => {
101+setActiveWorkspace("/workspace/a");
102+103+await expect(
104+withPinnedActivePluginRegistryWorkspaceDir(async () => {
105+setActiveWorkspace("/workspace/b");
106+throw new Error("workspace request failed");
107+}),
108+).rejects.toThrow("workspace request failed");
109+110+expect(getActivePluginRegistryWorkspaceDirFromState()).toBe("/workspace/b");
111+});
112+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。