|
| 1 | +const { config, overridesReady } = await import('@stacksjs/config') |
| 2 | +const { db, ensureDatabaseConfigLoaded, initializeDbConfig, resetDatabaseConnection } = await import('../../src/utils') |
| 3 | +const { logQuery } = await import('../../src/query-logger') |
| 4 | + |
| 5 | +await overridesReady |
| 6 | +await ensureDatabaseConfigLoaded() |
| 7 | +initializeDbConfig({ |
| 8 | + app: { env: 'test' }, |
| 9 | + database: { default: 'sqlite', connections: { sqlite: { database: ':memory:' } } }, |
| 10 | +}) |
| 11 | +config.database.queryLogging = { |
| 12 | + ...config.database.queryLogging, |
| 13 | + enabled: true, |
| 14 | + excludedQueries: [], |
| 15 | + analysis: { ...config.database.queryLogging?.analysis, enabled: false }, |
| 16 | +} |
| 17 | + |
| 18 | +async function readSite(value: number): Promise<void> { |
| 19 | + await logQuery({ query: { sql: `SELECT ${value}`, parameters: [value] }, queryDurationMillis: value }) |
| 20 | +} |
| 21 | + |
| 22 | +async function otherSite(value: number): Promise<void> { |
| 23 | + await logQuery({ query: { sql: `SELECT ${value}`, parameters: [value] }, queryDurationMillis: value }) |
| 24 | +} |
| 25 | + |
| 26 | +try { |
| 27 | + await db.unsafe(`CREATE TABLE query_logs ( |
| 28 | + id INTEGER PRIMARY KEY, query TEXT, normalized_query TEXT, duration REAL, |
| 29 | + connection TEXT, status TEXT, error TEXT, executed_at TEXT, bindings TEXT, |
| 30 | + trace TEXT, model TEXT, method TEXT, file TEXT, line INTEGER, memory_usage REAL |
| 31 | + )`).execute() |
| 32 | + |
| 33 | + const callers = [readSite, readSite, otherSite, readSite] |
| 34 | + for (const [index, caller] of callers.entries()) { |
| 35 | + await caller(index + 1) |
| 36 | + await Bun.sleep(2) |
| 37 | + } |
| 38 | + |
| 39 | + const records = await db.unsafe('SELECT * FROM query_logs ORDER BY id').execute() |
| 40 | + if (records.length !== 4) |
| 41 | + throw new Error(`Expected four persisted traces, received ${records.length}`) |
| 42 | + for (const [index, record] of records.entries()) { |
| 43 | + if (record.query !== `SELECT ${index + 1}` || record.bindings !== `[${index + 1}]` || record.duration !== index + 1) |
| 44 | + throw new Error('A repeated trace reused query-specific values') |
| 45 | + if (record.method !== (index === 2 ? 'otherSite' : 'readSite')) |
| 46 | + throw new Error(`Wrong caller for query ${index + 1}: ${record.method}`) |
| 47 | + if (!String(record.file).endsWith('/fixtures/query-trace.ts') || !(Number(record.line) > 0)) |
| 48 | + throw new Error('Missing caller location') |
| 49 | + if (!(Number(record.memory_usage) > 0) || !record.executed_at || record.status !== 'completed') |
| 50 | + throw new Error('Missing per-query diagnostics') |
| 51 | + } |
| 52 | + if (records[0]?.trace !== records[1]?.trace || records[0]?.trace !== records[3]?.trace || records[0]?.trace === records[2]?.trace) |
| 53 | + throw new Error('Captured traces did not follow the current caller') |
| 54 | + if (records[0]?.executed_at === records[1]?.executed_at) |
| 55 | + throw new Error('A repeated trace reused its execution timestamp') |
| 56 | + |
| 57 | + // Computed function names really appear in Bun stack traces. Each new |
| 58 | + // secret-shaped name must be sanitized, including a repeated invocation. |
| 59 | + for (const name of [`token_${'test'.repeat(6)}`, 'TEST_ONLY_OPAQUE_VALUE_'.repeat(3)]) { |
| 60 | + const secretSite = { |
| 61 | + [name]: () => { |
| 62 | + const pending = logQuery({ query: { sql: 'SELECT 10' }, queryDurationMillis: 10 }) |
| 63 | + // Keep the named frame: Bun eliminates a direct tail call here. |
| 64 | + return pending.finally(() => {}) |
| 65 | + }, |
| 66 | + } |
| 67 | + for (let repetition = 0; repetition < 2; repetition++) |
| 68 | + await secretSite[name]!() |
| 69 | + const traces = await db.unsafe('SELECT trace FROM query_logs WHERE query = ?', ['SELECT 10']).execute() |
| 70 | + if (traces.length !== 2 || traces.some(row => String(row.trace).includes(name) || !String(row.trace).includes('<redacted>'))) |
| 71 | + throw new Error('Secret-shaped caller name was persisted in a stack trace') |
| 72 | + await db.unsafe('DELETE FROM query_logs WHERE query = ?', ['SELECT 10']).execute() |
| 73 | + } |
| 74 | + console.log('query-trace-ok') |
| 75 | +} |
| 76 | +finally { |
| 77 | + resetDatabaseConnection() |
| 78 | +} |
0 commit comments