fix: execute mit optionalen Params, release(error) bei reset-Fehler, Tests ergänzt
This commit is contained in:
@@ -62,6 +62,27 @@ describe('withTenant', () => {
|
|||||||
await withTenant('abc123', async (client) => {
|
await withTenant('abc123', async (client) => {
|
||||||
await client.execute('DELETE FROM sessions WHERE expired = true')
|
await client.execute('DELETE FROM sessions WHERE expired = true')
|
||||||
})
|
})
|
||||||
expect(mockClient.query).toHaveBeenCalledWith('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))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { type QueryResultRow } from 'pg'
|
|||||||
import { pool } from './client'
|
import { pool } from './client'
|
||||||
|
|
||||||
interface TenantClient {
|
interface TenantClient {
|
||||||
execute: (sql: string) => Promise<void>
|
execute: (sql: string, params?: unknown[]) => Promise<void>
|
||||||
query: <T extends QueryResultRow = Record<string, unknown>>(sql: string, params: unknown[]) => Promise<T[]>
|
query: <T extends QueryResultRow = Record<string, unknown>>(sql: string, params: unknown[]) => Promise<T[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,17 +18,21 @@ export async function withTenant<T>(
|
|||||||
try {
|
try {
|
||||||
await client.query(`SET search_path = ${schema}, public`)
|
await client.query(`SET search_path = ${schema}, public`)
|
||||||
const tenantClient: TenantClient = {
|
const tenantClient: TenantClient = {
|
||||||
execute: async (sql) => { await client.query(sql) },
|
execute: async (sql, params?) => { await client.query(sql, params) },
|
||||||
query: async <T extends QueryResultRow = Record<string, unknown>>(sql: string, params: unknown[]) =>
|
query: async <T extends QueryResultRow = Record<string, unknown>>(sql: string, params: unknown[]) =>
|
||||||
client.query<T>(sql, params).then((r) => r.rows),
|
client.query<T>(sql, params).then((r) => r.rows),
|
||||||
}
|
}
|
||||||
return await fn(tenantClient)
|
return await fn(tenantClient)
|
||||||
} finally {
|
} finally {
|
||||||
|
let connectionBroken = false
|
||||||
try {
|
try {
|
||||||
await client.query('SET search_path = public')
|
await client.query('SET search_path = public')
|
||||||
} catch (resetErr) {
|
} catch (resetErr) {
|
||||||
|
connectionBroken = true
|
||||||
console.error({ msg: 'search_path reset failed', error: (resetErr as Error).message })
|
console.error({ msg: 'search_path reset failed', error: (resetErr as Error).message })
|
||||||
|
client.release(resetErr as Error)
|
||||||
}
|
}
|
||||||
|
if (!connectionBroken) {
|
||||||
try {
|
try {
|
||||||
client.release()
|
client.release()
|
||||||
} catch (releaseErr) {
|
} catch (releaseErr) {
|
||||||
@@ -36,3 +40,4 @@ export async function withTenant<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user