Download src/api/mcp-service/mcp-redacted-credentials.test.ts from SaylorTwift/openhands: direct link, hf CLI and curl.
- Browser
- Download file 9.3 kB
-
https://huggingface.co/SaylorTwift/openhands/resolve/main/src/api/mcp-service/mcp-redacted-credentials.test.ts
- Command line
-
hf download hf://SaylorTwift/openhands/src/api/mcp-service/mcp-redacted-credentials.test.ts
-
curl -L -o mcp-redacted-credentials.test.ts https://huggingface.co/SaylorTwift/openhands/resolve/main/src/api/mcp-service/mcp-redacted-credentials.test.ts
9.3 kB
| import { beforeEach, describe, expect, it, vi } from "vitest"; | |
| import SettingsService, { | |
| type SettingsApiResponse, | |
| } from "#/api/settings-service/settings-service.api"; | |
| import { substituteRedactedMcpCredentials } from "./mcp-redacted-credentials"; | |
| import { REDACTED_MCP_SECRET_VALUE } from "#/utils/mcp-config"; | |
| describe("substituteRedactedMcpCredentials", () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| }); | |
| it("preserves encrypted stdio env when the server is renamed", async () => { | |
| // Regression: renaming a stdio server left a redacted env value unchanged, | |
| // so the lookup by the new display name missed the stored entry and the | |
| // literal redaction placeholder overwrote the stored encrypted secret. | |
| vi.spyOn(SettingsService, "fetchSettingsFromApi").mockResolvedValue({ | |
| agent_settings: { | |
| mcp_config: { | |
| "old-name": { | |
| command: "npx", | |
| env: { API_KEY: "gAAAAA-encrypted-api-key" }, | |
| }, | |
| }, | |
| }, | |
| } as unknown as SettingsApiResponse); | |
| const result = await substituteRedactedMcpCredentials({ | |
| id: "old-name", | |
| type: "stdio", | |
| name: "new-name", | |
| command: "npx", | |
| env: { API_KEY: REDACTED_MCP_SECRET_VALUE }, | |
| }); | |
| expect(result.env).toEqual({ API_KEY: "gAAAAA-encrypted-api-key" }); | |
| expect(result.name).toBe("new-name"); | |
| }); | |
| it("does not restore another server's secret when renamed onto an existing name", async () => { | |
| // Renaming "alpha" onto "beta"'s name must still resolve alpha's stored | |
| // entry by stable key, not beta's entry. | |
| vi.spyOn(SettingsService, "fetchSettingsFromApi").mockResolvedValue({ | |
| agent_settings: { | |
| mcp_config: { | |
| alpha: { command: "npx", env: { TOKEN: "gAAAAA-alpha-token" } }, | |
| beta: { command: "npx", env: { TOKEN: "gAAAAA-beta-token" } }, | |
| }, | |
| }, | |
| } as unknown as SettingsApiResponse); | |
| const result = await substituteRedactedMcpCredentials({ | |
| id: "alpha", | |
| type: "stdio", | |
| name: "beta", | |
| command: "npx", | |
| env: { TOKEN: REDACTED_MCP_SECRET_VALUE }, | |
| }); | |
| expect(result.env).toEqual({ TOKEN: "gAAAAA-alpha-token" }); | |
| }); | |
| it("leaves typed (non-redacted) env values untouched", async () => { | |
| vi.spyOn(SettingsService, "fetchSettingsFromApi").mockResolvedValue({ | |
| agent_settings: { | |
| mcp_config: { | |
| "my-server": { | |
| command: "npx", | |
| env: { API_KEY: "gAAAAA-encrypted", REGION: "us-east-1" }, | |
| }, | |
| }, | |
| }, | |
| } as unknown as SettingsApiResponse); | |
| const result = await substituteRedactedMcpCredentials({ | |
| id: "my-server", | |
| type: "stdio", | |
| name: "my-server", | |
| command: "npx", | |
| env: { | |
| API_KEY: REDACTED_MCP_SECRET_VALUE, | |
| REGION: "eu-west-1", | |
| }, | |
| }); | |
| expect(result.env).toEqual({ | |
| API_KEY: "gAAAAA-encrypted", | |
| REGION: "eu-west-1", | |
| }); | |
| }); | |
| it("returns the server unchanged when no env value is redacted", async () => { | |
| const server = { | |
| id: "stdio-0", | |
| type: "stdio" as const, | |
| name: "my-server", | |
| command: "npx", | |
| env: { API_KEY: "plaintext" }, | |
| }; | |
| const result = await substituteRedactedMcpCredentials(server); | |
| expect(result).toBe(server); | |
| expect(SettingsService.fetchSettingsFromApi).not.toHaveBeenCalled(); | |
| }); | |
| it("keeps the placeholder when the stored stdio entry is missing", async () => { | |
| vi.spyOn(SettingsService, "fetchSettingsFromApi").mockResolvedValue({ | |
| agent_settings: { mcp_config: {} }, | |
| } as unknown as SettingsApiResponse); | |
| const result = await substituteRedactedMcpCredentials({ | |
| id: "stdio-0", | |
| type: "stdio", | |
| name: "new-name", | |
| command: "npx", | |
| env: { API_KEY: REDACTED_MCP_SECRET_VALUE }, | |
| }); | |
| expect(result.env).toEqual({ API_KEY: REDACTED_MCP_SECRET_VALUE }); | |
| }); | |
| it("replaces redacted OAuth state with the encrypted stored subtree", async () => { | |
| vi.spyOn(SettingsService, "fetchSettingsFromApi").mockResolvedValue({ | |
| agent_settings: { | |
| mcp_config: { | |
| "superhuman-mail": { | |
| url: "https://mcp.mail.superhuman.com/mcp", | |
| auth: { | |
| strategy: "oauth2", | |
| state: { | |
| tokens: { | |
| access_token: "gAAAAA-encrypted-access-token", | |
| refresh_token: "gAAAAA-encrypted-refresh-token", | |
| }, | |
| client_info: { | |
| client_id: "superhuman-client", | |
| client_secret: "gAAAAA-encrypted-client-secret", | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } as unknown as SettingsApiResponse); | |
| const result = await substituteRedactedMcpCredentials({ | |
| id: "superhuman-mail", | |
| type: "shttp", | |
| name: "superhuman-mail", | |
| url: "https://mcp.mail.superhuman.com/mcp", | |
| auth: { | |
| strategy: "oauth2", | |
| state: { | |
| tokens: { | |
| access_token: REDACTED_MCP_SECRET_VALUE, | |
| refresh_token: REDACTED_MCP_SECRET_VALUE, | |
| }, | |
| client_info: { | |
| client_id: "superhuman-client", | |
| client_secret: REDACTED_MCP_SECRET_VALUE, | |
| }, | |
| }, | |
| }, | |
| }); | |
| expect(result.auth).toEqual({ | |
| strategy: "oauth2", | |
| state: { | |
| tokens: { | |
| access_token: "gAAAAA-encrypted-access-token", | |
| refresh_token: "gAAAAA-encrypted-refresh-token", | |
| }, | |
| client_info: { | |
| client_id: "superhuman-client", | |
| client_secret: "gAAAAA-encrypted-client-secret", | |
| }, | |
| }, | |
| }); | |
| }); | |
| it("preserves fresh OAuth edits while substituting only redacted leaves", async () => { | |
| vi.spyOn(SettingsService, "fetchSettingsFromApi").mockResolvedValue({ | |
| agent_settings: { | |
| mcp_config: { | |
| "superhuman-mail": { | |
| url: "https://mcp.mail.superhuman.com/mcp", | |
| auth: { | |
| strategy: "oauth2", | |
| state: { | |
| tokens: { | |
| access_token: "gAAAAA-encrypted-access-token", | |
| refresh_token: "gAAAAA-encrypted-refresh-token", | |
| }, | |
| client_info: { | |
| client_id: "old-client-id", | |
| client_secret: "gAAAAA-encrypted-client-secret", | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } as unknown as SettingsApiResponse); | |
| const result = await substituteRedactedMcpCredentials({ | |
| id: "superhuman-mail", | |
| type: "shttp", | |
| name: "superhuman-mail", | |
| url: "https://mcp.mail.superhuman.com/mcp", | |
| auth: { | |
| strategy: "oauth2", | |
| state: { | |
| tokens: { | |
| access_token: REDACTED_MCP_SECRET_VALUE, | |
| refresh_token: REDACTED_MCP_SECRET_VALUE, | |
| }, | |
| client_info: { | |
| client_id: "new-client-id", | |
| client_secret: REDACTED_MCP_SECRET_VALUE, | |
| }, | |
| }, | |
| }, | |
| }); | |
| expect(result.auth).toEqual({ | |
| strategy: "oauth2", | |
| state: { | |
| tokens: { | |
| access_token: "gAAAAA-encrypted-access-token", | |
| refresh_token: "gAAAAA-encrypted-refresh-token", | |
| }, | |
| client_info: { | |
| client_id: "new-client-id", | |
| client_secret: "gAAAAA-encrypted-client-secret", | |
| }, | |
| }, | |
| }); | |
| }); | |
| it("preserves fresh header-auth edits while substituting only redacted leaves", async () => { | |
| vi.spyOn(SettingsService, "fetchSettingsFromApi").mockResolvedValue({ | |
| agent_settings: { | |
| mcp_config: { | |
| mail: { | |
| url: "https://mail.example/mcp", | |
| auth: { | |
| strategy: "header", | |
| headers: { | |
| "X-API-Key": "gAAAAA-encrypted-api-key", | |
| "X-Region": "us-east-1", | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } as unknown as SettingsApiResponse); | |
| const result = await substituteRedactedMcpCredentials({ | |
| id: "mail", | |
| type: "shttp", | |
| name: "mail", | |
| url: "https://mail.example/mcp", | |
| auth: { | |
| strategy: "header", | |
| headers: { | |
| "X-API-Key": REDACTED_MCP_SECRET_VALUE, | |
| "X-Region": "eu-west-1", | |
| }, | |
| }, | |
| }); | |
| expect(result.auth).toEqual({ | |
| strategy: "header", | |
| headers: { | |
| "X-API-Key": "gAAAAA-encrypted-api-key", | |
| "X-Region": "eu-west-1", | |
| }, | |
| }); | |
| }); | |
| it("replaces redacted raw remote headers with encrypted stored values", async () => { | |
| vi.spyOn(SettingsService, "fetchSettingsFromApi").mockResolvedValue({ | |
| agent_settings: { | |
| mcp_config: { | |
| github: { | |
| url: "https://github.example/mcp", | |
| headers: { | |
| Authorization: "Bearer gAAAAA-encrypted-github-token", | |
| }, | |
| }, | |
| }, | |
| }, | |
| } as unknown as SettingsApiResponse); | |
| const result = await substituteRedactedMcpCredentials({ | |
| id: "github", | |
| type: "shttp", | |
| name: "github", | |
| url: "https://github.example/mcp", | |
| headers: { Authorization: REDACTED_MCP_SECRET_VALUE }, | |
| }); | |
| expect(result.headers).toEqual({ | |
| Authorization: "Bearer gAAAAA-encrypted-github-token", | |
| }); | |
| }); | |
| }); | |