Skip to content

Commit b62945a

Browse files
Copilotsawka
andauthored
Add preview mock support for secrets RPCs (#3071)
The preview mock `WaveEnv` was missing the secrets RPC surface used by config/secrets flows. This adds mock implementations for the four secrets commands so preview and test environments can exercise secrets behavior without a backend. - **Added mock secrets RPC handlers** - Implemented `GetSecretsLinuxStorageBackendCommand` - Implemented `GetSecretsNamesCommand` - Implemented `GetSecretsCommand` - Implemented `SetSecretsCommand` - **Backed secrets RPCs with in-memory state** - Added a per-env in-memory secret store inside `makeMockRpc` - `SetSecretsCommand` now supports both upsert and delete (`null` => remove) - `GetSecretsNamesCommand` returns sorted secret names for stable behavior - `GetSecretsCommand` returns only requested keys that exist - `GetSecretsLinuxStorageBackendCommand` returns `"libsecret"` on Linux previews and `""` elsewhere - **Kept the change scoped to preview/mock behavior** - Wired platform into the mock RPC layer without changing production RPC behavior - Updated the mock environment contract comments to reflect the newly supported RPCs - **Added focused coverage** - Extended `frontend/preview/mock/mockwaveenv.test.ts` to cover set/get/list/delete semantics and Linux backend reporting ```ts const env = makeMockWaveEnv({ platform: "linux" }); await env.rpc.SetSecretsCommand(null as any, { OPENAI_API_KEY: "sk-test", ANTHROPIC_API_KEY: "anthropic-test", } as any); const names = await env.rpc.GetSecretsNamesCommand(null as any); // ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"] const secret = await env.rpc.GetSecretsCommand(null as any, ["OPENAI_API_KEY"]); // { OPENAI_API_KEY: "sk-test" } ``` <!-- START COPILOT CODING AGENT TIPS --> --- 📍 Connect Copilot coding agent with [Jira](https://gh.io/cca-jira-docs), [Azure Boards](https://gh.io/cca-azure-boards-docs) or [Linear](https://gh.io/cca-linear-docs) to delegate work to Copilot in one click without leaving your project management tool. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
1 parent 1ee3c46 commit b62945a

File tree

2 files changed

+61
-1
lines changed

2 files changed

+61
-1
lines changed

frontend/preview/mock/mockwaveenv.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,27 @@ describe("makeMockWaveEnv", () => {
9393
const imageBytes = base64ToArray(readPackets[1].data64);
9494
expect(Array.from(imageBytes.slice(0, 4))).toEqual([0x89, 0x50, 0x4e, 0x47]);
9595
});
96+
97+
it("implements secrets commands with in-memory storage", async () => {
98+
const { makeMockWaveEnv } = await import("./mockwaveenv");
99+
const env = makeMockWaveEnv({ platform: "linux" });
100+
101+
await env.rpc.SetSecretsCommand(null as any, {
102+
OPENAI_API_KEY: "sk-test",
103+
ANTHROPIC_API_KEY: "anthropic-test",
104+
} as any);
105+
106+
expect(await env.rpc.GetSecretsLinuxStorageBackendCommand(null as any)).toBe("libsecret");
107+
expect(await env.rpc.GetSecretsNamesCommand(null as any)).toEqual(["ANTHROPIC_API_KEY", "OPENAI_API_KEY"]);
108+
expect(await env.rpc.GetSecretsCommand(null as any, ["OPENAI_API_KEY", "MISSING_SECRET"])).toEqual({
109+
OPENAI_API_KEY: "sk-test",
110+
});
111+
112+
await env.rpc.SetSecretsCommand(null as any, { OPENAI_API_KEY: null } as any);
113+
114+
expect(await env.rpc.GetSecretsNamesCommand(null as any)).toEqual(["ANTHROPIC_API_KEY"]);
115+
expect(await env.rpc.GetSecretsCommand(null as any, ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"])).toEqual({
116+
ANTHROPIC_API_KEY: "anthropic-test",
117+
});
118+
});
96119
});

frontend/preview/mock/mockwaveenv.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { AllServiceTypes } from "@/app/store/services";
77
import { handleWaveEvent } from "@/app/store/wps";
88
import { RpcApiType } from "@/app/store/wshclientapi";
99
import { WaveEnv } from "@/app/waveenv/waveenv";
10-
import { PlatformMacOS, PlatformWindows } from "@/util/platformutil";
10+
import { PlatformLinux, PlatformMacOS, PlatformWindows } from "@/util/platformutil";
1111
import { Atom, atom, PrimitiveAtom, useAtomValue } from "jotai";
1212
import { DefaultFullConfig } from "./defaultconfig";
1313
import { DefaultMockFilesystem } from "./mockfilesystem";
@@ -20,8 +20,13 @@ import { previewElectronApi } from "./preview-electron-api";
2020
// - rpc.EventPublishCommand -- dispatches to handleWaveEvent(); works when the subscriber
2121
// is purely FE-based (registered via WPS on the frontend)
2222
// - rpc.GetMetaCommand -- reads .meta from the mock WOS atom for the given oref
23+
// - rpc.GetSecretsCommand -- reads secrets from an in-memory mock secret store
24+
// - rpc.GetSecretsLinuxStorageBackendCommand
25+
// returns "libsecret" on Linux previews and "" elsewhere
26+
// - rpc.GetSecretsNamesCommand -- lists secret names from the in-memory mock secret store
2327
// - rpc.SetMetaCommand -- writes .meta to the mock WOS atom (null values delete keys)
2428
// - rpc.SetConfigCommand -- merges settings into fullConfigAtom (null values delete keys)
29+
// - rpc.SetSecretsCommand -- writes/deletes secrets in the in-memory mock secret store
2530
// - rpc.UpdateTabNameCommand -- updates .name on the Tab WaveObj in the mock WOS
2631
// - rpc.UpdateWorkspaceTabIdsCommand -- updates .tabids on the Workspace WaveObj in the mock WOS
2732
//
@@ -172,11 +177,13 @@ type MockWosFns = {
172177
getWaveObjectAtom: <T extends WaveObj>(oref: string) => PrimitiveAtom<T>;
173178
mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => void;
174179
fullConfigAtom: PrimitiveAtom<FullConfigType>;
180+
platform: NodeJS.Platform;
175181
};
176182

177183
export function makeMockRpc(overrides: RpcOverrides, wos: MockWosFns): RpcApiType {
178184
const callDispatchMap = new Map<string, (...args: any[]) => Promise<any>>();
179185
const streamDispatchMap = new Map<string, (...args: any[]) => AsyncGenerator<any, void, boolean>>();
186+
const secrets = new Map<string, string>();
180187
const setCallHandler = (command: string, fn: (...args: any[]) => Promise<any>) => {
181188
callDispatchMap.set(command, fn);
182189
};
@@ -230,6 +237,35 @@ export function makeMockRpc(overrides: RpcOverrides, wos: MockWosFns): RpcApiTyp
230237
globalStore.set(wos.fullConfigAtom, { ...current, settings: updatedSettings as SettingsType });
231238
return null;
232239
});
240+
setCallHandler("getsecretslinuxstoragebackend", async () => {
241+
if (wos.platform !== PlatformLinux) {
242+
return "";
243+
}
244+
return "libsecret";
245+
});
246+
setCallHandler("getsecretsnames", async () => {
247+
return Array.from(secrets.keys()).sort();
248+
});
249+
setCallHandler("getsecrets", async (_client, data: string[]) => {
250+
const foundSecrets: Record<string, string> = {};
251+
for (const name of data ?? []) {
252+
const value = secrets.get(name);
253+
if (value != null) {
254+
foundSecrets[name] = value;
255+
}
256+
}
257+
return foundSecrets;
258+
});
259+
setCallHandler("setsecrets", async (_client, data: Record<string, string>) => {
260+
for (const [name, value] of Object.entries(data ?? {})) {
261+
if (value == null) {
262+
secrets.delete(name);
263+
continue;
264+
}
265+
secrets.set(name, value);
266+
}
267+
return null;
268+
});
233269
setCallHandler("updateworkspacetabids", async (_client, data: { args: [string, string[]] }) => {
234270
const [workspaceId, tabIds] = data.args;
235271
const wsORef = "workspace:" + workspaceId;
@@ -319,6 +355,7 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv {
319355
const mockWosFns: MockWosFns = {
320356
getWaveObjectAtom,
321357
fullConfigAtom: atoms.fullConfigAtom,
358+
platform,
322359
mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => {
323360
if (!waveObjectValueAtomCache.has(oref)) {
324361
waveObjectValueAtomCache.set(oref, atom(null as WaveObj));

0 commit comments

Comments
 (0)