Download __tests__/api/cloud/secrets-service.test.ts from SaylorTwift/openhands: direct link, hf CLI and curl.
- Browser
- Download file 5.49 kB
-
https://huggingface.co/SaylorTwift/openhands/resolve/main/__tests__/api/cloud/secrets-service.test.ts
- Command line
-
hf download hf://SaylorTwift/openhands/__tests__/api/cloud/secrets-service.test.ts
-
curl -L -o secrets-service.test.ts https://huggingface.co/SaylorTwift/openhands/resolve/main/__tests__/api/cloud/secrets-service.test.ts
5.49 kB
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | |
| import { | |
| __resetActiveStoreForTests, | |
| setActiveSelection, | |
| setRegisteredBackends, | |
| } from "#/api/backend-registry/active-store"; | |
| import type { Backend } from "#/api/backend-registry/types"; | |
| import { SecretsService } from "#/api/secrets-service"; | |
| import { | |
| getFetchCall, | |
| getJsonBody, | |
| mockJsonResponse, | |
| } from "./fetch-test-utils"; | |
| const cloudBackend: Backend = { | |
| id: "prod", | |
| name: "Production", | |
| host: "https://app.all-hands.dev", | |
| apiKey: "bearer-token", | |
| kind: "cloud", | |
| }; | |
| const originalFetch = global.fetch; | |
| const fetchMock = vi.fn(); | |
| beforeEach(() => { | |
| window.localStorage.clear(); | |
| __resetActiveStoreForTests(); | |
| setRegisteredBackends([cloudBackend]); | |
| setActiveSelection({ backendId: cloudBackend.id }); | |
| fetchMock.mockReset(); | |
| fetchMock.mockResolvedValue(mockJsonResponse({})); | |
| global.fetch = fetchMock as typeof fetch; | |
| }); | |
| afterEach(() => { | |
| window.localStorage.clear(); | |
| __resetActiveStoreForTests(); | |
| fetchMock.mockReset(); | |
| global.fetch = originalFetch; | |
| }); | |
| describe("SecretsService against cloud backend", () => { | |
| it("paginates getSecrets directly and returns the merged list", async () => { | |
| fetchMock | |
| .mockResolvedValueOnce( | |
| mockJsonResponse({ | |
| items: [ | |
| { name: "ALPHA", description: "first" }, | |
| { name: "BETA", description: "second" }, | |
| ], | |
| next_page_id: "BETA", | |
| }), | |
| ) | |
| .mockResolvedValueOnce( | |
| mockJsonResponse({ | |
| items: [{ name: "GAMMA", description: "third" }], | |
| next_page_id: null, | |
| }), | |
| ); | |
| const secrets = await SecretsService.getSecrets(); | |
| expect(fetchMock).toHaveBeenCalledTimes(2); | |
| const [firstUrl, firstInit] = getFetchCall(fetchMock, 0); | |
| expect(firstInit).toMatchObject({ | |
| method: "GET", | |
| headers: { Authorization: "Bearer bearer-token" }, | |
| }); | |
| expect(firstUrl).toMatch( | |
| /^https:\/\/app\.all-hands\.dev\/api\/v1\/secrets\/search\?/, | |
| ); | |
| expect(firstUrl).not.toContain("page_id="); | |
| const [secondUrl] = getFetchCall(fetchMock, 1); | |
| expect(secondUrl).toContain("page_id=BETA"); | |
| expect(secrets.map((s) => s.name)).toEqual(["ALPHA", "BETA", "GAMMA"]); | |
| }); | |
| it("creates a secret via direct POST /api/v1/secrets", async () => { | |
| await SecretsService.createSecret( | |
| "OPENAI_API_KEY", | |
| "sk-test", | |
| "OpenAI key", | |
| ); | |
| const [url, init] = getFetchCall(fetchMock); | |
| expect(url).toBe(`${cloudBackend.host}/api/v1/secrets`); | |
| expect(init).toMatchObject({ | |
| method: "POST", | |
| headers: { Authorization: "Bearer bearer-token" }, | |
| }); | |
| expect(getJsonBody(init)).toEqual({ | |
| name: "OPENAI_API_KEY", | |
| value: "sk-test", | |
| description: "OpenAI key", | |
| }); | |
| }); | |
| it("updates a secret via PUT /api/v1/secrets/{id} with name + description only", async () => { | |
| // The form/hook calls updateSecret(secretToEdit, newName, description). | |
| await SecretsService.updateSecret("OLD_NAME", "NEW_NAME", "renamed"); | |
| const [url, init] = getFetchCall(fetchMock); | |
| expect(url).toBe(`${cloudBackend.host}/api/v1/secrets/OLD_NAME`); | |
| expect(init).toMatchObject({ | |
| method: "PUT", | |
| headers: { Authorization: "Bearer bearer-token" }, | |
| }); | |
| expect(getJsonBody(init)).toEqual({ | |
| name: "NEW_NAME", | |
| description: "renamed", | |
| }); | |
| // No value supplied, so nothing overwrites the stored one. | |
| expect(fetchMock).toHaveBeenCalledTimes(1); | |
| }); | |
| it("overwrites the value with a POST following the PUT", async () => { | |
| // Fresh Response per call: a Response body can only be consumed once. | |
| fetchMock.mockImplementation(() => Promise.resolve(mockJsonResponse({}))); | |
| await SecretsService.updateSecret( | |
| "API_KEY", | |
| "API_KEY", | |
| "Demo secret", | |
| "new-value", | |
| ); | |
| expect(fetchMock).toHaveBeenCalledTimes(2); | |
| const [putUrl, putInit] = getFetchCall(fetchMock, 0); | |
| expect(putUrl).toBe(`${cloudBackend.host}/api/v1/secrets/API_KEY`); | |
| expect(putInit).toMatchObject({ method: "PUT" }); | |
| const [postUrl, postInit] = getFetchCall(fetchMock, 1); | |
| expect(postUrl).toBe(`${cloudBackend.host}/api/v1/secrets`); | |
| expect(postInit).toMatchObject({ method: "POST" }); | |
| expect(getJsonBody(postInit)).toEqual({ | |
| name: "API_KEY", | |
| value: "new-value", | |
| description: "Demo secret", | |
| }); | |
| }); | |
| it("deletes a secret via direct DELETE /api/v1/secrets/{id}", async () => { | |
| await SecretsService.deleteSecret("token with space"); | |
| const [url, init] = getFetchCall(fetchMock); | |
| expect(url).toBe( | |
| `${cloudBackend.host}/api/v1/secrets/token%20with%20space`, | |
| ); | |
| expect(init).toMatchObject({ | |
| method: "DELETE", | |
| headers: { Authorization: "Bearer bearer-token" }, | |
| }); | |
| }); | |
| it("treats a delete 404 as success (secret already gone)", async () => { | |
| // Fresh Response per attempt: the retry helper re-fetches and a | |
| // Response body can only be consumed once. Fake timers skip the | |
| // retry backoff sleeps. | |
| fetchMock.mockImplementation(() => | |
| Promise.resolve(mockJsonResponse({ detail: "Secret not found" }, 404)), | |
| ); | |
| vi.useFakeTimers(); | |
| try { | |
| const assertion = expect( | |
| SecretsService.deleteSecret("ALREADY_GONE"), | |
| ).resolves.toBeUndefined(); | |
| await vi.runAllTimersAsync(); | |
| await assertion; | |
| } finally { | |
| vi.useRealTimers(); | |
| } | |
| }); | |
| }); | |