Files
HiveGrid-Wireframes/hivegrid-cloud/test/isolation/fixtures/user-session-isolation.test.ts
T

98 lines
4.5 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import {
createDeniedIsolationError,
expectJsonResponse,
expectStatusLog,
isolationAdminSession,
isolationAdminUser,
isolationApiKeyLogA,
isolationApiKeyLogB,
isolationInactiveApiKey,
isolationInactiveApiKeyLog,
isolationInvalidBearerToken,
isolationRevokedBearerSession,
isolationTenantA,
isolationTenantARelationship,
isolationTenantARow,
isolationTenantB,
isolationTenantBRelationship,
isolationTenantBRow,
makeAdminHostGetRequest,
makeInactiveApiKeyGetRequest,
makeInvalidBearerSessionGetRequest,
makeRevokedBearerSessionGetRequest,
makeTenantASessionJsonRequest,
resetIsolationMocks,
type IsolationMocks,
} from "./user-session-isolation";
describe("user-session isolation fixtures", () => {
it("keeps tenant A and tenant B rows and relationships distinct", () => {
expect(isolationTenantARow.tenantId).toBe(isolationTenantA.id);
expect(isolationTenantBRow.tenantId).toBe(isolationTenantB.id);
expect(isolationTenantARow.tenantId).not.toBe(isolationTenantBRow.tenantId);
expect(isolationTenantARelationship.parent.item.tenantId).toBe(isolationTenantA.id);
expect(isolationTenantBRelationship.parent.item.tenantId).toBe(isolationTenantB.id);
});
it("keeps admin fixtures tenant-neutral and cross-tenant capable", () => {
expect(isolationAdminUser.tenantId).toBe(null);
expect(isolationAdminSession.tenantId).toBe(null);
expect(isolationAdminSession.isSuperAdmin).toBe(true);
expect(isolationAdminSession.canAccessTenants).toEqual([isolationTenantA.id, isolationTenantB.id]);
});
it("keeps inactive API-key fixtures distinct from active tenant keys", () => {
expect(isolationInactiveApiKey.active).toBe(false);
expect(isolationInactiveApiKey.id).toBe(isolationInactiveApiKeyLog.apiKeyId);
expect(isolationInactiveApiKeyLog.id).not.toBe(isolationApiKeyLogA.id);
expect(isolationInactiveApiKeyLog.id).not.toBe(isolationApiKeyLogB.id);
expect(isolationInactiveApiKeyLog.apiKeyId).not.toBe(isolationApiKeyLogA.apiKeyId);
expect(isolationInactiveApiKeyLog.apiKeyId).not.toBe(isolationApiKeyLogB.apiKeyId);
});
it("builds tenant/session JSON requests and parses JSON responses", async () => {
const request = makeTenantASessionJsonRequest("POST", "/api/parents", { name: "Tenant A" });
expect(request.method).toBe("POST");
expect(request.url).toBe("https://example.test/api/parents");
await expect(request.json()).resolves.toEqual({ name: "Tenant A" });
const body = await expectJsonResponse<{ ok: true }>(Response.json({ ok: true }, { status: 201, headers: { "Server-Timing": "unit" } }), 201);
expect(body).toEqual({ ok: true });
});
it("builds admin-host and revoked credential requests with synthetic headers", () => {
const adminRequest = makeAdminHostGetRequest("/api-keys/logs");
const inactiveApiKeyRequest = makeInactiveApiKeyGetRequest("/api/parents");
const revokedBearerRequest = makeRevokedBearerSessionGetRequest("/api/parents");
const invalidBearerRequest = makeInvalidBearerSessionGetRequest("/api/parents");
expect(adminRequest.url).toBe("https://admin.example.test/api-keys/logs");
expect(inactiveApiKeyRequest.headers.get("Authorization")).toBe(`Bearer ${isolationInactiveApiKey.key}`);
expect(revokedBearerRequest.headers.get("Cookie")).toBe(`session=${isolationRevokedBearerSession.token}`);
expect(invalidBearerRequest.headers.get("Cookie")).toBe(`session=${isolationInvalidBearerToken}`);
expect(isolationRevokedBearerSession.token).not.toBe(isolationInvalidBearerToken);
});
it("resets shared mocks and supports denied access status assertions", async () => {
const mocks = {
createMetrics: vi.fn(),
getTranslations: vi.fn(),
setApiKeyLogStatus: vi.fn(),
reportUsage: vi.fn(),
} satisfies IsolationMocks;
resetIsolationMocks(mocks);
const metrics = (await mocks.createMetrics()) as { getServerTimingHeader: () => Record<string, string> };
const deniedError = createDeniedIsolationError("API Key does not have access to tenant B row", 403, isolationApiKeyLogA);
await mocks.setApiKeyLogStatus(isolationApiKeyLogA.id, { status: deniedError.status, error: deniedError.message, startTime: performance.now() });
expect(metrics.getServerTimingHeader()).toEqual({ "Server-Timing": "unit;dur=1" });
await expect(mocks.getTranslations()).resolves.toEqual({ t: expect.any(Function) });
expectStatusLog(mocks.setApiKeyLogStatus, isolationApiKeyLogA.id, 403, deniedError.message);
});
});