Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/modules/api/services/ApiKeyLogService.rls.test.ts
T

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();
});
});