67 lines
2.7 KiB
TypeScript
67 lines
2.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { getRlsExecutionContext, runWithRlsContext } from "@hivegrid/db/rls-context.server";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
findScopes: vi.fn(),
|
|
deleteForTenant: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@hivegrid/db/accounts.server", () => ({
|
|
adminGetAllLegacyTenantSummaries: vi.fn(async () => []),
|
|
}));
|
|
|
|
vi.mock("@hivegrid/db/api-keys.server", () => ({
|
|
countLegacyApiKeyLogs: vi.fn(),
|
|
deleteTenantLegacyApiKeyLogsByIds: mocks.deleteForTenant,
|
|
findLegacyApiKeyLogTenantScopesByIds: mocks.findScopes,
|
|
getLegacyApiKeyLogAdminDetails: vi.fn(),
|
|
groupLegacyApiKeyLogs: vi.fn(),
|
|
tenantLegacyApiKeyLogScope: (tenantId: string) => ({ tenantId }),
|
|
}));
|
|
|
|
import ApiKeyLogService from "./ApiKeyLogService";
|
|
|
|
describe("ApiKeyLogService admin RLS deletion", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mocks.deleteForTenant.mockImplementation(async ({ tenantId }: { tenantId: string }) => {
|
|
expect(getRlsExecutionContext()).toMatchObject({ userId: "operator_1", tenantId });
|
|
return { count: 1 };
|
|
});
|
|
});
|
|
|
|
it("deletes each selected log only inside its owning tenant context", async () => {
|
|
mocks.findScopes.mockResolvedValue([
|
|
{ id: "log_a", tenantId: "tenant_a" },
|
|
{ id: "log_b", tenantId: "tenant_b" },
|
|
]);
|
|
|
|
const result = await runWithRlsContext({ userId: "operator_1" }, () => ApiKeyLogService.adminDeleteMany(["log_a", "log_b"]));
|
|
|
|
expect(result).toEqual({ deletedCount: 2, rejectedTenantlessIds: [] });
|
|
expect(mocks.deleteForTenant).toHaveBeenCalledTimes(2);
|
|
expect(mocks.deleteForTenant).toHaveBeenCalledWith({ ids: ["log_a"], tenantId: "tenant_a" });
|
|
expect(mocks.deleteForTenant).toHaveBeenCalledWith({ ids: ["log_b"], tenantId: "tenant_b" });
|
|
});
|
|
|
|
it("rejects the whole batch when it contains a tenantless platform log", async () => {
|
|
mocks.findScopes.mockResolvedValue([
|
|
{ id: "log_a", tenantId: "tenant_a" },
|
|
{ id: "log_platform", tenantId: null },
|
|
]);
|
|
|
|
const result = await runWithRlsContext({ userId: "operator_1" }, () => ApiKeyLogService.adminDeleteMany(["log_a", "log_platform"]));
|
|
|
|
expect(result).toEqual({ deletedCount: 0, rejectedTenantlessIds: ["log_platform"] });
|
|
expect(mocks.deleteForTenant).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("fails closed outside an unscoped authenticated operator request", async () => {
|
|
await expect(ApiKeyLogService.adminDeleteMany(["log_a"])).rejects.toThrow("unscoped authenticated operator context");
|
|
await expect(runWithRlsContext({ userId: "operator_1", tenantId: "tenant_a" }, () => ApiKeyLogService.adminDeleteMany(["log_a"]))).rejects.toThrow(
|
|
"unscoped authenticated operator context"
|
|
);
|
|
expect(mocks.findScopes).not.toHaveBeenCalled();
|
|
});
|
|
});
|