Files
coding-starter/src/server/db/tenant.test.ts

89 lines
3.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
// Pool aus client.ts mocken — gibt einen fake PoolClient zurück
const mockClient = vi.hoisted(() => ({
query: vi.fn(),
release: vi.fn(),
}))
vi.mock('./client', () => ({
pool: {
connect: vi.fn().mockResolvedValue(mockClient),
},
}))
import { withTenant } from './tenant'
describe('withTenant', () => {
beforeEach(() => {
vi.clearAllMocks()
mockClient.query.mockResolvedValue({ rows: [] })
})
it('setzt search_path auf tenant-Schema', async () => {
await withTenant('abc123', async (client) => {
await client.query('SELECT 1', [])
})
expect(mockClient.query).toHaveBeenCalledWith('SET search_path = tenant_abc123, public')
})
it('setzt search_path zurück auf public nach Ausführung', async () => {
await withTenant('abc123', async (client) => {
await client.query('SELECT 1', [])
})
expect(mockClient.query).toHaveBeenLastCalledWith('SET search_path = public')
})
it('setzt search_path zurück auch bei Fehler', async () => {
await expect(
withTenant('abc123', async () => { throw new Error('DB-Fehler') })
).rejects.toThrow('DB-Fehler')
expect(mockClient.query).toHaveBeenLastCalledWith('SET search_path = public')
expect(mockClient.release).toHaveBeenCalled()
})
it('gibt Rückgabewert der Funktion zurück', async () => {
const result = await withTenant('abc123', async () => 'testdata')
expect(result).toBe('testdata')
})
it('wirft bei ungültiger tenantId', async () => {
await expect(
withTenant('invalid-tenant!', async () => 'x')
).rejects.toThrow('Ungültige tenantId')
})
it('released PoolClient nach Ausführung', async () => {
await withTenant('tenant1', async () => {})
expect(mockClient.release).toHaveBeenCalledOnce()
})
it('execute delegiert an den dedizierten PoolClient', async () => {
await withTenant('abc123', async (client) => {
await client.execute('DELETE FROM sessions WHERE expired = true')
})
expect(mockClient.query).toHaveBeenCalledWith('DELETE FROM sessions WHERE expired = true', undefined)
})
it('execute akzeptiert optionale Parameter', async () => {
await withTenant('abc123', async (client) => {
await client.execute('UPDATE t SET x = $1 WHERE id = $2', ['v', '1'])
})
expect(mockClient.query).toHaveBeenCalledWith('UPDATE t SET x = $1 WHERE id = $2', ['v', '1'])
})
it('zerstört Verbindung bei search_path-Reset-Fehler (kein Recycling)', async () => {
mockClient.query
.mockResolvedValueOnce({ rows: [] }) // SET search_path = tenant_...
.mockResolvedValueOnce({ rows: [] }) // fn-query
.mockRejectedValueOnce(new Error('reset-fehler')) // SET search_path = public schlägt fehl
await withTenant('abc123', async (client) => {
await client.query('SELECT 1', [])
})
// release() muss mit Error aufgerufen worden sein (Verbindung zerstört, nicht recycelt)
expect(mockClient.release).toHaveBeenCalledWith(expect.any(Error))
})
})