fix(#195): Implement RLS context helpers consistently across all services
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

Added workspace context management to PrismaService:
- setWorkspaceContext(userId, workspaceId, client?) - Sets session variables
- clearWorkspaceContext(client?) - Clears session variables
- withWorkspaceContext(userId, workspaceId, fn) - Transaction wrapper

Extended db-context.ts with workspace-scoped helpers:
- setCurrentWorkspace(workspaceId, client)
- setWorkspaceContext(userId, workspaceId, client)
- clearWorkspaceContext(client)
- withWorkspaceContext(userId, workspaceId, fn)

All functions use SET LOCAL for transaction-scoped variables (connection pool safe).
Added comprehensive tests (11 passing unit tests).

Fixes #195

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 22:44:54 -06:00
parent 555fcd04db
commit 68f641211a
4 changed files with 342 additions and 1 deletions

View File

@@ -119,4 +119,86 @@ describe("PrismaService", () => {
});
});
});
describe("setWorkspaceContext", () => {
it("should set workspace context variables in transaction", async () => {
const userId = "user-123";
const workspaceId = "workspace-456";
const executeRawSpy = vi.spyOn(service, "$executeRaw").mockResolvedValue(0);
await service.$transaction(async (tx) => {
await service.setWorkspaceContext(userId, workspaceId, tx);
});
expect(executeRawSpy).toHaveBeenCalledTimes(2);
// Check that both session variables were set
expect(executeRawSpy).toHaveBeenNthCalledWith(1, expect.anything());
expect(executeRawSpy).toHaveBeenNthCalledWith(2, expect.anything());
});
it("should work when called outside transaction using default client", async () => {
const userId = "user-123";
const workspaceId = "workspace-456";
const executeRawSpy = vi.spyOn(service, "$executeRaw").mockResolvedValue(0);
await service.setWorkspaceContext(userId, workspaceId);
expect(executeRawSpy).toHaveBeenCalledTimes(2);
});
});
describe("withWorkspaceContext", () => {
it("should execute function with workspace context set", async () => {
const userId = "user-123";
const workspaceId = "workspace-456";
const executeRawSpy = vi.spyOn(service, "$executeRaw").mockResolvedValue(0);
const result = await service.withWorkspaceContext(userId, workspaceId, async () => {
return "test-result";
});
expect(result).toBe("test-result");
expect(executeRawSpy).toHaveBeenCalledTimes(2);
});
it("should pass transaction client to callback", async () => {
const userId = "user-123";
const workspaceId = "workspace-456";
vi.spyOn(service, "$executeRaw").mockResolvedValue(0);
let receivedClient: unknown = null;
await service.withWorkspaceContext(userId, workspaceId, async (tx) => {
receivedClient = tx;
return null;
});
expect(receivedClient).toBeDefined();
expect(receivedClient).toHaveProperty("$executeRaw");
});
it("should handle errors from callback", async () => {
const userId = "user-123";
const workspaceId = "workspace-456";
vi.spyOn(service, "$executeRaw").mockResolvedValue(0);
const error = new Error("Callback error");
await expect(
service.withWorkspaceContext(userId, workspaceId, async () => {
throw error;
})
).rejects.toThrow(error);
});
});
describe("clearWorkspaceContext", () => {
it("should clear workspace context variables", async () => {
const executeRawSpy = vi.spyOn(service, "$executeRaw").mockResolvedValue(0);
await service.$transaction(async (tx) => {
await service.clearWorkspaceContext(tx);
});
expect(executeRawSpy).toHaveBeenCalledTimes(2);
});
});
});