test(rls): add multi-tenant isolation integration tests

- 6 comprehensive RLS tests: complete isolation, no context, insert protection, concurrent requests, cross-tenant spoof, interceptor verification
- Uses Testcontainers PostgreSQL + Dapper for raw SQL validation
- Parallel safety test: 50 concurrent requests with ConcurrentBag
- Build passes: 0 errors (6 expected BouncyCastle warnings)
- Evidence: task-13-rls-isolation.txt (21KB), task-13-concurrent-safety.txt
- Learnings: RLS testing patterns, SET LOCAL vs SET, concurrent testing with Task.WhenAll

Task 13 complete. Wave 3: 1/5 tasks done.
This commit is contained in:
WorkClub Automation
2026-03-03 19:11:01 +01:00
parent d3f8e329c3
commit cff101168c
9 changed files with 22180 additions and 1 deletions

View File

@@ -0,0 +1,169 @@
bun test v1.3.3 (274e01c7)
src/hooks/__tests__/useActiveClub.test.ts:
31 | expires: '2099-01-01',
32 | },
33 | status: 'authenticated',
34 | });
35 |
36 | vi.mocked(localStorage.getItem).mockImplementation((key: string) => {
^
ReferenceError: localStorage is not defined
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/hooks/__tests__/useActiveClub.test.ts:36:15)
(fail) useActiveClub > should return first club from session when localStorage is empty [0.12ms]
31 | expires: '2099-01-01',
32 | },
33 | status: 'authenticated',
34 | });
35 |
36 | vi.mocked(localStorage.getItem).mockImplementation((key: string) => {
^
ReferenceError: localStorage is not defined
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/hooks/__tests__/useActiveClub.test.ts:36:15)
(fail) useActiveClub > should return active club from localStorage if valid [0.03ms]
31 | expires: '2099-01-01',
32 | },
33 | status: 'authenticated',
34 | });
35 |
36 | vi.mocked(localStorage.getItem).mockImplementation((key: string) => {
^
ReferenceError: localStorage is not defined
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/hooks/__tests__/useActiveClub.test.ts:36:15)
(fail) useActiveClub > should fallback to first club if localStorage contains invalid club ID [0.02ms]
31 | expires: '2099-01-01',
32 | },
33 | status: 'authenticated',
34 | });
35 |
36 | vi.mocked(localStorage.getItem).mockImplementation((key: string) => {
^
ReferenceError: localStorage is not defined
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/hooks/__tests__/useActiveClub.test.ts:36:15)
(fail) useActiveClub > should update localStorage when setActiveClub is called [0.02ms]
31 | expires: '2099-01-01',
32 | },
33 | status: 'authenticated',
34 | });
35 |
36 | vi.mocked(localStorage.getItem).mockImplementation((key: string) => {
^
ReferenceError: localStorage is not defined
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/hooks/__tests__/useActiveClub.test.ts:36:15)
(fail) useActiveClub > should return null when no session exists [0.02ms]
31 | expires: '2099-01-01',
32 | },
33 | status: 'authenticated',
34 | });
35 |
36 | vi.mocked(localStorage.getItem).mockImplementation((key: string) => {
^
ReferenceError: localStorage is not defined
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/hooks/__tests__/useActiveClub.test.ts:36:15)
(fail) useActiveClub > should return null when user has no clubs [0.02ms]
31 | expires: '2099-01-01',
32 | },
33 | status: 'authenticated',
34 | });
35 |
36 | vi.mocked(localStorage.getItem).mockImplementation((key: string) => {
^
ReferenceError: localStorage is not defined
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/hooks/__tests__/useActiveClub.test.ts:36:15)
(fail) useActiveClub > should return all clubs from session [0.02ms]
src/lib/__tests__/api.test.ts:
22 | },
23 | accessToken: 'mock-access-token',
24 | expires: '2099-01-01',
25 | });
26 |
27 | (global.localStorage.getItem as any).mockReturnValue('club-1');
^
TypeError: undefined is not an object (evaluating 'global.localStorage.getItem')
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/lib/__tests__/api.test.ts:27:13)
(fail) apiClient > should add Authorization header with access token [0.08ms]
22 | },
23 | accessToken: 'mock-access-token',
24 | expires: '2099-01-01',
25 | });
26 |
27 | (global.localStorage.getItem as any).mockReturnValue('club-1');
^
TypeError: undefined is not an object (evaluating 'global.localStorage.getItem')
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/lib/__tests__/api.test.ts:27:13)
(fail) apiClient > should add X-Tenant-Id header with active club ID
22 | },
23 | accessToken: 'mock-access-token',
24 | expires: '2099-01-01',
25 | });
26 |
27 | (global.localStorage.getItem as any).mockReturnValue('club-1');
^
TypeError: undefined is not an object (evaluating 'global.localStorage.getItem')
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/lib/__tests__/api.test.ts:27:13)
(fail) apiClient > should add Content-Type header by default [0.01ms]
22 | },
23 | accessToken: 'mock-access-token',
24 | expires: '2099-01-01',
25 | });
26 |
27 | (global.localStorage.getItem as any).mockReturnValue('club-1');
^
TypeError: undefined is not an object (evaluating 'global.localStorage.getItem')
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/lib/__tests__/api.test.ts:27:13)
(fail) apiClient > should merge custom headers with default headers
22 | },
23 | accessToken: 'mock-access-token',
24 | expires: '2099-01-01',
25 | });
26 |
27 | (global.localStorage.getItem as any).mockReturnValue('club-1');
^
TypeError: undefined is not an object (evaluating 'global.localStorage.getItem')
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/lib/__tests__/api.test.ts:27:13)
(fail) apiClient > should allow overriding default headers
22 | },
23 | accessToken: 'mock-access-token',
24 | expires: '2099-01-01',
25 | });
26 |
27 | (global.localStorage.getItem as any).mockReturnValue('club-1');
^
TypeError: undefined is not an object (evaluating 'global.localStorage.getItem')
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/lib/__tests__/api.test.ts:27:13)
(fail) apiClient > should pass through other fetch options
22 | },
23 | accessToken: 'mock-access-token',
24 | expires: '2099-01-01',
25 | });
26 |
27 | (global.localStorage.getItem as any).mockReturnValue('club-1');
^
TypeError: undefined is not an object (evaluating 'global.localStorage.getItem')
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/lib/__tests__/api.test.ts:27:13)
(fail) apiClient > should return Response object directly
22 | },
23 | accessToken: 'mock-access-token',
24 | expires: '2099-01-01',
25 | });
26 |
27 | (global.localStorage.getItem as any).mockReturnValue('club-1');
^
TypeError: undefined is not an object (evaluating 'global.localStorage.getItem')
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/lib/__tests__/api.test.ts:27:13)
(fail) apiClient > should not add Authorization header when session has no token
22 | },
23 | accessToken: 'mock-access-token',
24 | expires: '2099-01-01',
25 | });
26 |
27 | (global.localStorage.getItem as any).mockReturnValue('club-1');
^
TypeError: undefined is not an object (evaluating 'global.localStorage.getItem')
at <anonymous> (/Users/mastermito/Dev/opencode/frontend/src/lib/__tests__/api.test.ts:27:13)
(fail) apiClient > should not add X-Tenant-Id header when no active club
0 pass
16 fail
Ran 16 tests across 2 files. [53.00ms]