File size: 4,139 Bytes
fd2c364 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { HttpError } from "@openhands/typescript-client";
import {
__resetActiveStoreForTests,
setRegisteredBackends,
} from "#/api/backend-registry/active-store";
import {
getCloudOrganizationMember,
getCloudOrganizations,
getCurrentCloudApiKey,
} from "#/api/cloud/organization-service.api";
import type { Backend } from "#/api/backend-registry/types";
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();
function mockJsonResponse(data: unknown, status = 200): Response {
return new Response(JSON.stringify(data), {
status,
statusText: status === 200 ? "OK" : "Error",
headers: { "content-type": "application/json" },
});
}
beforeEach(() => {
window.localStorage.clear();
__resetActiveStoreForTests();
setRegisteredBackends([]);
fetchMock.mockReset();
global.fetch = fetchMock as typeof fetch;
});
afterEach(() => {
window.localStorage.clear();
__resetActiveStoreForTests();
fetchMock.mockReset();
global.fetch = originalFetch;
});
describe("cloud organization-service", () => {
it("getCloudOrganizations calls the cloud API directly and returns normalized data", async () => {
fetchMock.mockResolvedValueOnce(
mockJsonResponse({
items: [{ id: "org-1", name: "Personal" }],
current_org_id: "org-1",
}),
);
const result = await getCloudOrganizations(cloudBackend);
expect(fetchMock).toHaveBeenCalledOnce();
const [url, init] = fetchMock.mock.calls[0]!;
expect(url).toBe(`${cloudBackend.host}/api/organizations`);
expect(init).toMatchObject({
method: "GET",
headers: { Authorization: "Bearer bearer-token" },
});
expect(result).toEqual({
items: [{ id: "org-1", name: "Personal" }],
currentOrgId: "org-1",
});
});
it("getCurrentCloudApiKey hits /api/keys/current and returns the bound orgId", async () => {
fetchMock.mockResolvedValueOnce(
mockJsonResponse({
id: "key-1",
name: "k",
org_id: "org-bound",
user_id: "user-1",
auth_type: "bearer",
}),
);
const result = await getCurrentCloudApiKey(cloudBackend);
const [url, init] = fetchMock.mock.calls[0]!;
expect(url).toBe(`${cloudBackend.host}/api/keys/current`);
expect(init).toMatchObject({
method: "GET",
headers: { Authorization: "Bearer bearer-token" },
});
expect(result).toEqual({ orgId: "org-bound", isLegacyKey: false });
});
it("getCurrentCloudApiKey treats an upstream 400 as a legacy key (no binding)", async () => {
fetchMock.mockResolvedValueOnce(mockJsonResponse({ detail: "bad" }, 400));
const result = await getCurrentCloudApiKey(cloudBackend);
expect(result).toEqual({ orgId: null, isLegacyKey: true });
});
it("getCurrentCloudApiKey rethrows non-400 upstream errors (e.g. revoked key)", async () => {
fetchMock.mockResolvedValueOnce(
mockJsonResponse({ detail: "unauthorized" }, 401),
);
await expect(getCurrentCloudApiKey(cloudBackend)).rejects.toBeInstanceOf(
HttpError,
);
});
it("getCloudOrganizationMember hits /api/organizations/{orgId}/members/{userId} and returns the member", async () => {
// Arrange
const member = {
org_id: "org-1",
user_id: "user-1",
email: "jdoe@acme.com",
role: "member",
status: "active",
};
fetchMock.mockResolvedValueOnce(mockJsonResponse(member));
// Act
const result = await getCloudOrganizationMember(
"org-1",
"user-1",
cloudBackend,
);
// Assert
expect(fetchMock).toHaveBeenCalledOnce();
const [url, init] = fetchMock.mock.calls[0]!;
expect(url).toBe(
`${cloudBackend.host}/api/organizations/org-1/members/user-1`,
);
expect(init).toMatchObject({
method: "GET",
headers: { Authorization: "Bearer bearer-token" },
});
expect(result).toEqual(member);
});
});
|