98 lines
4.5 KiB
TypeScript
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);
|
|
});
|
|
});
|