源码快照 @ 2026-09-02 (source-only, 剔除非源码素材)
This commit is contained in:
@@ -0,0 +1,494 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
|
||||
import { clearDenSession } from "../src/app/lib/den";
|
||||
import { createOpenworkServerClient } from "../src/app/lib/openwork-server";
|
||||
import { createClient } from "../src/app/lib/opencode";
|
||||
import type { ResolvedWorkspaceEndpoint } from "../src/app/lib/workspace-endpoint";
|
||||
import type { ProviderListItem, WorkspaceDisplay } from "../src/app/types";
|
||||
import { createSessionOpenworkServer } from "../src/react-app/domains/connections/provider-auth/session-openwork-server";
|
||||
import { createProviderAuthStore } from "../src/react-app/domains/connections/provider-auth/store";
|
||||
|
||||
/**
|
||||
* Regression tests for #3671 (org-published LLM providers never reach
|
||||
* signed-in desktops): the session route — the app's default surface — used to
|
||||
* feed the provider-auth store a fabricated openwork-server snapshot without
|
||||
* the `providerSync` capability or host-token auth, so
|
||||
* `serverHandlesProviderSync()` was permanently false there. After sign-in
|
||||
* the store therefore never PUT the Den session to the local server
|
||||
* (server-side sync never started) and instead ran the legacy renderer-side
|
||||
* import loop against Den.
|
||||
*
|
||||
* These tests drive the real store through the real session-route snapshot
|
||||
* builder (`createSessionOpenworkServer`) and assert the store takes the
|
||||
* server-side path for local endpoints: PUT /den-session with the host token,
|
||||
* POST /cloud-provider-sync/run, and zero renderer-side Den provider fetches.
|
||||
*/
|
||||
|
||||
const originalWindow = globalThis.window;
|
||||
const originalFetch = globalThis.fetch;
|
||||
const originalConsoleInfo = console.info;
|
||||
const originalDeployment = process.env.VITE_OPENWORK_DEPLOYMENT;
|
||||
|
||||
const LOCAL_SERVER_ORIGIN = "http://127.0.0.1:7899";
|
||||
const REMOTE_SERVER_ORIGIN = "https://worker.example";
|
||||
|
||||
type RecordedRequest = {
|
||||
url: string;
|
||||
method: string;
|
||||
body: string | null;
|
||||
headers: Record<string, string>;
|
||||
};
|
||||
|
||||
function memoryStorage(): Storage {
|
||||
const map = new Map<string, string>();
|
||||
return {
|
||||
get length() {
|
||||
return map.size;
|
||||
},
|
||||
clear() {
|
||||
map.clear();
|
||||
},
|
||||
getItem(key: string) {
|
||||
return map.get(key) ?? null;
|
||||
},
|
||||
key(index: number) {
|
||||
return Array.from(map.keys())[index] ?? null;
|
||||
},
|
||||
removeItem(key: string) {
|
||||
map.delete(key);
|
||||
},
|
||||
setItem(key: string, value: string) {
|
||||
map.set(key, value);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function installWindow(): Storage {
|
||||
const localStorage = memoryStorage();
|
||||
const listeners = new Map<string, Set<EventListener>>();
|
||||
Object.defineProperty(globalThis, "window", {
|
||||
configurable: true,
|
||||
value: {
|
||||
addEventListener: (type: string, listener: EventListener) => {
|
||||
const registered = listeners.get(type) ?? new Set<EventListener>();
|
||||
registered.add(listener);
|
||||
listeners.set(type, registered);
|
||||
},
|
||||
removeEventListener: (type: string, listener: EventListener) => {
|
||||
listeners.get(type)?.delete(listener);
|
||||
},
|
||||
dispatchEvent: (event: Event) => {
|
||||
for (const listener of listeners.get(event.type) ?? []) listener(event);
|
||||
return true;
|
||||
},
|
||||
localStorage,
|
||||
location: { origin: "https://self-hosted.example" },
|
||||
__OPENWORK_GATEWAY__: undefined,
|
||||
},
|
||||
});
|
||||
return localStorage;
|
||||
}
|
||||
|
||||
function installCloudSession(storage: Storage) {
|
||||
storage.setItem("openwork.den.baseUrl", "https://den.example");
|
||||
storage.setItem("openwork.den.authToken", "den-token");
|
||||
storage.setItem("openwork.den.activeOrgId", "org_test");
|
||||
}
|
||||
|
||||
function getRequestUrl(input: RequestInfo | URL): string {
|
||||
if (input instanceof URL) return input.toString();
|
||||
if (typeof input === "string") return input;
|
||||
return input.url;
|
||||
}
|
||||
|
||||
function getRequestMethod(input: RequestInfo | URL, init?: RequestInit): string {
|
||||
if (init?.method) return init.method;
|
||||
if (input instanceof Request) return input.method;
|
||||
return "GET";
|
||||
}
|
||||
|
||||
function normalizeHeaders(init?: RequestInit): Record<string, string> {
|
||||
const raw = init?.headers;
|
||||
if (!raw) return {};
|
||||
if (raw instanceof Headers) {
|
||||
const entries: Record<string, string> = {};
|
||||
raw.forEach((value, key) => {
|
||||
entries[key.toLowerCase()] = value;
|
||||
});
|
||||
return entries;
|
||||
}
|
||||
if (Array.isArray(raw)) {
|
||||
return Object.fromEntries(raw.map(([key, value]) => [key.toLowerCase(), value]));
|
||||
}
|
||||
return Object.fromEntries(Object.entries(raw).map(([key, value]) => [key.toLowerCase(), String(value)]));
|
||||
}
|
||||
|
||||
function jsonResponse(payload: unknown, status = 200) {
|
||||
return new Response(JSON.stringify(payload), {
|
||||
status,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
function cloudProviderPayload() {
|
||||
return {
|
||||
id: "lpr_test",
|
||||
source: "custom",
|
||||
providerId: "openai",
|
||||
name: "Team OpenAI",
|
||||
providerConfig: { env: ["OPENAI_API_KEY"] },
|
||||
hasApiKey: true,
|
||||
apiKey: "sk-test",
|
||||
models: [{ id: "gpt-test", name: "GPT Test", config: {}, createdAt: null }],
|
||||
createdAt: null,
|
||||
updatedAt: "2026-08-10T00:00:00.000Z",
|
||||
};
|
||||
}
|
||||
|
||||
function installFetchMock(
|
||||
requests: RecordedRequest[],
|
||||
options: {
|
||||
runStatuses?: Array<{ status: "applied" | "noop" | "failed" | "no_session"; message?: string }>;
|
||||
providerResponse?: Promise<Response>;
|
||||
} = {},
|
||||
) {
|
||||
let runIndex = 0;
|
||||
Object.defineProperty(globalThis, "fetch", {
|
||||
configurable: true,
|
||||
value: async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = new URL(getRequestUrl(input));
|
||||
const method = getRequestMethod(input, init);
|
||||
requests.push({
|
||||
url: url.toString(),
|
||||
method,
|
||||
body: typeof init?.body === "string" ? init.body : null,
|
||||
headers: normalizeHeaders(init),
|
||||
});
|
||||
|
||||
if (url.origin === "https://den.example" && url.pathname === "/api/den/v1/llm-providers") {
|
||||
return options.providerResponse ?? jsonResponse({ llmProviders: [cloudProviderPayload()] });
|
||||
}
|
||||
if (url.origin === "https://den.example" && url.pathname === "/api/den/v1/llm-providers/lpr_test/connect") {
|
||||
return jsonResponse({ llmProvider: cloudProviderPayload() });
|
||||
}
|
||||
if (url.pathname === "/den-session" && method === "PUT") {
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
if (url.pathname === "/cloud-provider-sync/run" && method === "POST") {
|
||||
const statuses = options.runStatuses ?? [{ status: "noop" }];
|
||||
const result = statuses[Math.min(runIndex, statuses.length - 1)];
|
||||
runIndex += 1;
|
||||
return jsonResponse(result);
|
||||
}
|
||||
if (url.pathname === "/cloud-provider-sync/status" && method === "GET") {
|
||||
return jsonResponse({ hasSession: true, lastRun: null, providers: [] });
|
||||
}
|
||||
if (url.pathname === "/workspace/ws_1/config" && method === "GET") {
|
||||
return jsonResponse({ opencode: {}, openwork: {} });
|
||||
}
|
||||
if (url.pathname === "/workspace/ws_1/config" && method === "PATCH") {
|
||||
return jsonResponse({ updatedAt: 1 });
|
||||
}
|
||||
if (url.pathname === "/workspace/ws_1/opencode-config") {
|
||||
return jsonResponse(null);
|
||||
}
|
||||
if (url.pathname === "/env") {
|
||||
return jsonResponse({ ok: true });
|
||||
}
|
||||
if (url.pathname === "/workspace/ws_1/engine/reload") {
|
||||
return jsonResponse({ ok: true, reloadedAt: 1 });
|
||||
}
|
||||
if (url.pathname === "/global/health") {
|
||||
return jsonResponse({ healthy: true, version: "1.17.11" });
|
||||
}
|
||||
if (url.pathname === "/provider") {
|
||||
return jsonResponse({ all: [], connected: [], default: {} });
|
||||
}
|
||||
if (url.pathname === "/config") {
|
||||
return jsonResponse({ disabled_providers: [] });
|
||||
}
|
||||
return jsonResponse({});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function makeEndpoint(options: { origin: string; isRemote: boolean }): ResolvedWorkspaceEndpoint {
|
||||
const client = createOpenworkServerClient({ baseUrl: options.origin, token: "client-token" });
|
||||
const mountedBaseUrl = `${options.origin}/workspace/ws_1`;
|
||||
return {
|
||||
baseUrl: options.origin,
|
||||
token: "client-token",
|
||||
workspaceId: "ws_1",
|
||||
isRemote: options.isRemote,
|
||||
client,
|
||||
mountedBaseUrl,
|
||||
opencodeBaseUrl: `${mountedBaseUrl}/opencode`,
|
||||
};
|
||||
}
|
||||
|
||||
function createSessionRouteStore(options: {
|
||||
endpoint: ResolvedWorkspaceEndpoint | null;
|
||||
hostToken: string;
|
||||
connectedProviderIds?: string[];
|
||||
}) {
|
||||
const opencodeClient = createClient("https://engine.example", "/tmp/workspace_test", {
|
||||
token: "engine-token",
|
||||
mode: "openwork",
|
||||
});
|
||||
const workspace = {
|
||||
id: "workspace_test",
|
||||
name: "Test workspace",
|
||||
path: "/tmp/workspace_test",
|
||||
preset: "default",
|
||||
workspaceType: options.endpoint?.isRemote ? "remote" : "local",
|
||||
} satisfies WorkspaceDisplay;
|
||||
let providers: ProviderListItem[] = [];
|
||||
let providerDefaults: Record<string, string> = {};
|
||||
let providerConnectedIds: string[] = options.connectedProviderIds ?? [];
|
||||
let disabledProviders: string[] = [];
|
||||
|
||||
return createProviderAuthStore({
|
||||
client: () => opencodeClient,
|
||||
providers: () => providers,
|
||||
providerDefaults: () => providerDefaults,
|
||||
providerConnectedIds: () => providerConnectedIds,
|
||||
disabledProviders: () => disabledProviders,
|
||||
checkDesktopAppRestriction: () => false,
|
||||
selectedWorkspaceDisplay: () => workspace,
|
||||
providerBaseUrl: () => "https://engine.example",
|
||||
selectedWorkspaceRoot: () => "/tmp/workspace_test",
|
||||
runtimeWorkspaceId: () => "ws_1",
|
||||
// The exact snapshot builder the session route mounts.
|
||||
openworkServer: createSessionOpenworkServer({
|
||||
endpoint: () => options.endpoint,
|
||||
hostToken: () => options.hostToken,
|
||||
}),
|
||||
setProviders: (value) => {
|
||||
providers = value;
|
||||
},
|
||||
setProviderDefaults: (value) => {
|
||||
providerDefaults = value;
|
||||
},
|
||||
setProviderConnectedIds: (value) => {
|
||||
providerConnectedIds = value;
|
||||
},
|
||||
setDisabledProviders: (value) => {
|
||||
disabledProviders = value;
|
||||
},
|
||||
markOpencodeConfigReloadRequired: () => undefined,
|
||||
});
|
||||
}
|
||||
|
||||
describe("session-route cloud provider sync wiring", () => {
|
||||
beforeEach(() => {
|
||||
process.env.VITE_OPENWORK_DEPLOYMENT = "web";
|
||||
console.info = () => undefined;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(globalThis, "window", { configurable: true, value: originalWindow });
|
||||
Object.defineProperty(globalThis, "fetch", { configurable: true, value: originalFetch });
|
||||
console.info = originalConsoleInfo;
|
||||
if (originalDeployment === undefined) delete process.env.VITE_OPENWORK_DEPLOYMENT;
|
||||
else process.env.VITE_OPENWORK_DEPLOYMENT = originalDeployment;
|
||||
});
|
||||
|
||||
test("startup hydrates assigned organization models without a workspace endpoint", async () => {
|
||||
const storage = installWindow();
|
||||
installCloudSession(storage);
|
||||
const requests: RecordedRequest[] = [];
|
||||
installFetchMock(requests);
|
||||
const store = createSessionRouteStore({
|
||||
endpoint: null,
|
||||
hostToken: "",
|
||||
});
|
||||
const assignedModelsLoaded = new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error("assigned models did not load")), 1_000);
|
||||
const unsubscribe = store.subscribe(() => {
|
||||
if (store.getSnapshot().cloudOrgProviders.length === 0) return;
|
||||
clearTimeout(timeout);
|
||||
unsubscribe();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
store.start();
|
||||
await assignedModelsLoaded;
|
||||
|
||||
expect(store.getSnapshot().cloudOrgProviders).toMatchObject([
|
||||
{
|
||||
id: "lpr_test",
|
||||
models: [{ id: "gpt-test", name: "GPT Test" }],
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
requests.filter((request) => request.url === "https://den.example/api/den/v1/llm-providers"),
|
||||
).toHaveLength(1);
|
||||
expect(requests.filter((request) => new URL(request.url).pathname === "/cloud-provider-sync/run")).toHaveLength(0);
|
||||
store.dispose();
|
||||
});
|
||||
|
||||
test("clearing the Den session clears assigned organization models", async () => {
|
||||
const storage = installWindow();
|
||||
installCloudSession(storage);
|
||||
const requests: RecordedRequest[] = [];
|
||||
installFetchMock(requests);
|
||||
const store = createSessionRouteStore({
|
||||
endpoint: null,
|
||||
hostToken: "",
|
||||
});
|
||||
const assignedModelsLoaded = new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error("assigned models did not load")), 1_000);
|
||||
const unsubscribe = store.subscribe(() => {
|
||||
if (store.getSnapshot().cloudOrgProviders.length === 0) return;
|
||||
clearTimeout(timeout);
|
||||
unsubscribe();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
store.start();
|
||||
await assignedModelsLoaded;
|
||||
clearDenSession();
|
||||
|
||||
expect(store.getSnapshot().cloudOrgProviders).toEqual([]);
|
||||
expect(store.getSnapshot().importedCloudProviders).toEqual({});
|
||||
store.dispose();
|
||||
});
|
||||
|
||||
test("logout removes connected provider credentials and resets their saved default", async () => {
|
||||
const storage = installWindow();
|
||||
installCloudSession(storage);
|
||||
storage.setItem("openwork.defaultModel", "anthropic/claude-fable-5");
|
||||
const requests: RecordedRequest[] = [];
|
||||
installFetchMock(requests);
|
||||
const store = createSessionRouteStore({
|
||||
endpoint: null,
|
||||
hostToken: "",
|
||||
connectedProviderIds: ["opencode", "anthropic"],
|
||||
});
|
||||
|
||||
store.start();
|
||||
clearDenSession();
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
if (requests.some((request) =>
|
||||
request.method === "DELETE" && new URL(request.url).pathname === "/auth/anthropic"
|
||||
)) break;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
expect(
|
||||
requests.filter((request) =>
|
||||
request.method === "DELETE" && new URL(request.url).pathname === "/auth/anthropic"
|
||||
),
|
||||
).toHaveLength(1);
|
||||
expect(storage.getItem("openwork.defaultModel")).not.toBe("anthropic/claude-fable-5");
|
||||
store.dispose();
|
||||
});
|
||||
|
||||
test("an organization provider request started before logout cannot restore stale models", async () => {
|
||||
const storage = installWindow();
|
||||
installCloudSession(storage);
|
||||
const requests: RecordedRequest[] = [];
|
||||
let resolveProviderResponse: (response: Response) => void = () => undefined;
|
||||
const providerResponse = new Promise<Response>((resolve) => {
|
||||
resolveProviderResponse = resolve;
|
||||
});
|
||||
installFetchMock(requests, { providerResponse });
|
||||
const store = createSessionRouteStore({
|
||||
endpoint: null,
|
||||
hostToken: "",
|
||||
});
|
||||
|
||||
store.start();
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
if (requests.some((request) => request.url === "https://den.example/api/den/v1/llm-providers")) break;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
expect(
|
||||
requests.filter((request) => request.url === "https://den.example/api/den/v1/llm-providers"),
|
||||
).toHaveLength(1);
|
||||
clearDenSession();
|
||||
resolveProviderResponse(jsonResponse({ llmProviders: [cloudProviderPayload()] }));
|
||||
await providerResponse;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(store.getSnapshot().cloudOrgProviders).toEqual([]);
|
||||
expect(store.getSnapshot().importedCloudProviders).toEqual({});
|
||||
store.dispose();
|
||||
});
|
||||
|
||||
test("a local endpoint with a host token pushes the Den session and syncs server-side after sign-in", async () => {
|
||||
const storage = installWindow();
|
||||
installCloudSession(storage);
|
||||
const requests: RecordedRequest[] = [];
|
||||
installFetchMock(requests, { runStatuses: [{ status: "no_session" }, { status: "applied" }] });
|
||||
const store = createSessionRouteStore({
|
||||
endpoint: makeEndpoint({ origin: LOCAL_SERVER_ORIGIN, isRemote: false }),
|
||||
hostToken: "host-token-live",
|
||||
});
|
||||
|
||||
expect(await store.runCloudProviderSync("sign_in")).toEqual({ outcome: "handled_server_side" });
|
||||
|
||||
const sessionPuts = requests.filter(
|
||||
(request) => request.method === "PUT" && new URL(request.url).pathname === "/den-session",
|
||||
);
|
||||
expect(sessionPuts).toHaveLength(1);
|
||||
expect(new URL(sessionPuts[0]?.url ?? "").origin).toBe(LOCAL_SERVER_ORIGIN);
|
||||
expect(sessionPuts[0]?.headers["x-openwork-host-token"]).toBe("host-token-live");
|
||||
expect(sessionPuts[0]?.body).toBe(JSON.stringify({
|
||||
baseUrl: "https://den.example/api/den",
|
||||
token: "den-token",
|
||||
orgId: "org_test",
|
||||
}));
|
||||
|
||||
const runPosts = requests.filter(
|
||||
(request) => request.method === "POST" && new URL(request.url).pathname === "/cloud-provider-sync/run",
|
||||
);
|
||||
expect(runPosts).toHaveLength(2);
|
||||
expect(runPosts.every((request) => request.headers["x-openwork-host-token"] === "host-token-live")).toBe(true);
|
||||
|
||||
// Server-side sync means the renderer never fetches Den providers itself.
|
||||
expect(requests.filter((request) => request.url.includes("/v1/llm-providers"))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("falls back to the persisted host token for loopback servers when live host info is absent", async () => {
|
||||
const storage = installWindow();
|
||||
installCloudSession(storage);
|
||||
storage.setItem("openwork.server.hostToken", "host-token-stored");
|
||||
const requests: RecordedRequest[] = [];
|
||||
installFetchMock(requests, { runStatuses: [{ status: "no_session" }, { status: "noop" }] });
|
||||
const store = createSessionRouteStore({
|
||||
endpoint: makeEndpoint({ origin: LOCAL_SERVER_ORIGIN, isRemote: false }),
|
||||
hostToken: "",
|
||||
});
|
||||
|
||||
expect(await store.runCloudProviderSync("sign_in")).toEqual({ outcome: "handled_server_side" });
|
||||
|
||||
const sessionPuts = requests.filter(
|
||||
(request) => request.method === "PUT" && new URL(request.url).pathname === "/den-session",
|
||||
);
|
||||
expect(sessionPuts).toHaveLength(1);
|
||||
expect(sessionPuts[0]?.headers["x-openwork-host-token"]).toBe("host-token-stored");
|
||||
});
|
||||
|
||||
test("remote workspaces never receive the desktop's Den session and keep the legacy client path", async () => {
|
||||
const storage = installWindow();
|
||||
installCloudSession(storage);
|
||||
// Even a (stale) persisted local host token must not leak to a remote worker.
|
||||
storage.setItem("openwork.server.hostToken", "host-token-stored");
|
||||
const requests: RecordedRequest[] = [];
|
||||
installFetchMock(requests);
|
||||
const store = createSessionRouteStore({
|
||||
endpoint: makeEndpoint({ origin: REMOTE_SERVER_ORIGIN, isRemote: true }),
|
||||
hostToken: "",
|
||||
});
|
||||
|
||||
await store.runCloudProviderSync("sign_in");
|
||||
|
||||
expect(requests.filter((request) => new URL(request.url).pathname === "/den-session")).toHaveLength(0);
|
||||
expect(requests.filter((request) => new URL(request.url).pathname === "/cloud-provider-sync/run")).toHaveLength(0);
|
||||
// The legacy renderer-side reconciliation still runs for remote workspaces.
|
||||
expect(requests.some((request) => request.url === "https://den.example/api/den/v1/llm-providers")).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user