Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions dev-packages/e2e-tests/test-applications/nuxt-3/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ export default defineNuxtConfig({
},
},
nitro: {
experimental: {
database: true,
},
database: {
default: {
connector: 'sqlite',
options: { name: 'db' },
},
users: {
connector: 'sqlite',
options: { name: 'users_db' },
},
analytics: {
connector: 'sqlite',
options: { name: 'analytics_db' },
},
},
storage: {
'test-storage': {
driver: 'memory',
Expand Down
3 changes: 2 additions & 1 deletion dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
]
},
"volta": {
"extends": "../../package.json"
"extends": "../../package.json",
"node": "22.20.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { defineEventHandler, getQuery, useDatabase } from '#imports';

export default defineEventHandler(async event => {
const query = getQuery(event);
const method = query.method as string;

switch (method) {
case 'default-db': {
// Test default database instance
const db = useDatabase();
await db.exec('CREATE TABLE IF NOT EXISTS default_table (id INTEGER PRIMARY KEY, data TEXT)');
await db.exec(`INSERT OR REPLACE INTO default_table (id, data) VALUES (1, 'default data')`);
const stmt = db.prepare('SELECT * FROM default_table WHERE id = ?');
const result = await stmt.get(1);
return { success: true, database: 'default', result };
}

case 'users-db': {
// Test named database instance 'users'
const usersDb = useDatabase('users');
await usersDb.exec(
'CREATE TABLE IF NOT EXISTS user_profiles (id INTEGER PRIMARY KEY, username TEXT, email TEXT)',
);
await usersDb.exec(
`INSERT OR REPLACE INTO user_profiles (id, username, email) VALUES (1, 'john_doe', '[email protected]')`,
);
const stmt = usersDb.prepare('SELECT * FROM user_profiles WHERE id = ?');
const result = await stmt.get(1);
return { success: true, database: 'users', result };
}

case 'analytics-db': {
// Test named database instance 'analytics'
const analyticsDb = useDatabase('analytics');
await analyticsDb.exec(
'CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, event_name TEXT, count INTEGER)',
);
await analyticsDb.exec(`INSERT OR REPLACE INTO events (id, event_name, count) VALUES (1, 'page_view', 100)`);
const stmt = analyticsDb.prepare('SELECT * FROM events WHERE id = ?');
const result = await stmt.get(1);
return { success: true, database: 'analytics', result };
}

case 'multiple-dbs': {
// Test operations across multiple databases in a single request
const defaultDb = useDatabase();
const usersDb = useDatabase('users');
const analyticsDb = useDatabase('analytics');

// Create tables and insert data in all databases
await defaultDb.exec('CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY, token TEXT)');
await defaultDb.exec(`INSERT OR REPLACE INTO sessions (id, token) VALUES (1, 'session-token-123')`);

await usersDb.exec('CREATE TABLE IF NOT EXISTS accounts (id INTEGER PRIMARY KEY, account_name TEXT)');
await usersDb.exec(`INSERT OR REPLACE INTO accounts (id, account_name) VALUES (1, 'Premium Account')`);

await analyticsDb.exec(
'CREATE TABLE IF NOT EXISTS metrics (id INTEGER PRIMARY KEY, metric_name TEXT, value REAL)',
);
await analyticsDb.exec(
`INSERT OR REPLACE INTO metrics (id, metric_name, value) VALUES (1, 'conversion_rate', 0.25)`,
);

// Query from all databases
const sessionResult = await defaultDb.prepare('SELECT * FROM sessions WHERE id = ?').get(1);
const accountResult = await usersDb.prepare('SELECT * FROM accounts WHERE id = ?').get(1);
const metricResult = await analyticsDb.prepare('SELECT * FROM metrics WHERE id = ?').get(1);

return {
success: true,
results: {
default: sessionResult,
users: accountResult,
analytics: metricResult,
},
};
}

case 'sql-template-multi': {
// Test SQL template tag across multiple databases
const defaultDb = useDatabase();
const usersDb = useDatabase('users');

await defaultDb.exec('CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, message TEXT)');
await usersDb.exec('CREATE TABLE IF NOT EXISTS audit_logs (id INTEGER PRIMARY KEY, action TEXT)');

const defaultResult = await defaultDb.sql`INSERT INTO logs (message) VALUES (${'test message'})`;
const usersResult = await usersDb.sql`INSERT INTO audit_logs (action) VALUES (${'user_login'})`;

return {
success: true,
results: {
default: defaultResult,
users: usersResult,
},
};
}

default:
return { error: 'Unknown method' };
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { defineEventHandler, getQuery, useDatabase } from '#imports';

export default defineEventHandler(async event => {
const db = useDatabase();
const query = getQuery(event);
const method = query.method as string;

switch (method) {
case 'prepare-get': {
await db.exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
await db.exec(`INSERT OR REPLACE INTO users (id, name, email) VALUES (1, 'Test User', '[email protected]')`);
const stmt = db.prepare('SELECT * FROM users WHERE id = ?');
const result = await stmt.get(1);
return { success: true, result };
}

case 'prepare-all': {
await db.exec('CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT, price REAL)');
await db.exec(`INSERT OR REPLACE INTO products (id, name, price) VALUES
(1, 'Product A', 10.99),
(2, 'Product B', 20.50),
(3, 'Product C', 15.25)`);
const stmt = db.prepare('SELECT * FROM products WHERE price > ?');
const results = await stmt.all(10);
return { success: true, count: results.length, results };
}

case 'prepare-run': {
await db.exec('CREATE TABLE IF NOT EXISTS orders (id INTEGER PRIMARY KEY, customer TEXT, amount REAL)');
const stmt = db.prepare('INSERT INTO orders (customer, amount) VALUES (?, ?)');
const result = await stmt.run('John Doe', 99.99);
return { success: true, result };
}

case 'prepare-bind': {
await db.exec('CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, category TEXT, value INTEGER)');
await db.exec(`INSERT OR REPLACE INTO items (id, category, value) VALUES
(1, 'electronics', 100),
(2, 'books', 50),
(3, 'electronics', 200)`);
const stmt = db.prepare('SELECT * FROM items WHERE category = ?');
const boundStmt = stmt.bind('electronics');
const results = await boundStmt.all();
return { success: true, count: results.length, results };
}

case 'sql': {
await db.exec('CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY, content TEXT, created_at TEXT)');
const timestamp = new Date().toISOString();
const results = await db.sql`INSERT INTO messages (content, created_at) VALUES (${'Hello World'}, ${timestamp})`;
return { success: true, results };
}

case 'exec': {
await db.exec('CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, message TEXT, level TEXT)');
const result = await db.exec(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`);
return { success: true, result };
}

case 'error': {
const stmt = db.prepare('SELECT * FROM nonexistent_table WHERE invalid_column = ?');
await stmt.get(1);
return { success: false, message: 'Should have thrown an error' };
}

default:
return { error: 'Unknown method' };
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

test.describe('multiple database instances', () => {
test('instruments default database instance', async ({ request }) => {
const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction === 'GET /api/db-multi-test';
});

await request.get('/api/db-multi-test?method=default-db');

const transaction = await transactionPromise;

const dbSpans = transaction.spans?.filter(span => span.op === 'db.query');

expect(dbSpans).toBeDefined();
expect(dbSpans!.length).toBeGreaterThan(0);

// Check that we have the SELECT span
const selectSpan = dbSpans?.find(span => span.description?.includes('SELECT * FROM default_table'));
expect(selectSpan).toBeDefined();
expect(selectSpan?.op).toBe('db.query');
expect(selectSpan?.data?.['db.system.name']).toBe('sqlite');
expect(selectSpan?.data?.['sentry.origin']).toBe('auto.db.nuxt');
});

test('instruments named database instance (users)', async ({ request }) => {
const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction === 'GET /api/db-multi-test';
});

await request.get('/api/db-multi-test?method=users-db');

const transaction = await transactionPromise;

const dbSpans = transaction.spans?.filter(span => span.op === 'db.query');

expect(dbSpans).toBeDefined();
expect(dbSpans!.length).toBeGreaterThan(0);

// Check that we have the SELECT span from users database
const selectSpan = dbSpans?.find(span => span.description?.includes('SELECT * FROM user_profiles'));
expect(selectSpan).toBeDefined();
expect(selectSpan?.op).toBe('db.query');
expect(selectSpan?.data?.['db.system.name']).toBe('sqlite');
expect(selectSpan?.data?.['sentry.origin']).toBe('auto.db.nuxt');
});

test('instruments named database instance (analytics)', async ({ request }) => {
const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction === 'GET /api/db-multi-test';
});

await request.get('/api/db-multi-test?method=analytics-db');

const transaction = await transactionPromise;

const dbSpans = transaction.spans?.filter(span => span.op === 'db.query');

expect(dbSpans).toBeDefined();
expect(dbSpans!.length).toBeGreaterThan(0);

// Check that we have the SELECT span from analytics database
const selectSpan = dbSpans?.find(span => span.description?.includes('SELECT * FROM events'));
expect(selectSpan).toBeDefined();
expect(selectSpan?.op).toBe('db.query');
expect(selectSpan?.data?.['db.system.name']).toBe('sqlite');
expect(selectSpan?.data?.['sentry.origin']).toBe('auto.db.nuxt');
});

test('instruments multiple database instances in single request', async ({ request }) => {
const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction === 'GET /api/db-multi-test';
});

await request.get('/api/db-multi-test?method=multiple-dbs');

const transaction = await transactionPromise;

const dbSpans = transaction.spans?.filter(span => span.op === 'db.query');

expect(dbSpans).toBeDefined();
expect(dbSpans!.length).toBeGreaterThan(0);

// Check that we have spans from all three databases
const sessionSpan = dbSpans?.find(span => span.description?.includes('SELECT * FROM sessions'));
const accountSpan = dbSpans?.find(span => span.description?.includes('SELECT * FROM accounts'));
const metricSpan = dbSpans?.find(span => span.description?.includes('SELECT * FROM metrics'));

expect(sessionSpan).toBeDefined();
expect(sessionSpan?.op).toBe('db.query');
expect(sessionSpan?.data?.['db.system.name']).toBe('sqlite');

expect(accountSpan).toBeDefined();
expect(accountSpan?.op).toBe('db.query');
expect(accountSpan?.data?.['db.system.name']).toBe('sqlite');

expect(metricSpan).toBeDefined();
expect(metricSpan?.op).toBe('db.query');
expect(metricSpan?.data?.['db.system.name']).toBe('sqlite');

// All should have the same origin
expect(sessionSpan?.data?.['sentry.origin']).toBe('auto.db.nuxt');
expect(accountSpan?.data?.['sentry.origin']).toBe('auto.db.nuxt');
expect(metricSpan?.data?.['sentry.origin']).toBe('auto.db.nuxt');
});

test('instruments SQL template tag across multiple databases', async ({ request }) => {
const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction === 'GET /api/db-multi-test';
});

await request.get('/api/db-multi-test?method=sql-template-multi');

const transaction = await transactionPromise;

const dbSpans = transaction.spans?.filter(span => span.op === 'db.query');

expect(dbSpans).toBeDefined();
expect(dbSpans!.length).toBeGreaterThan(0);

// Check that we have INSERT spans from both databases
const logsInsertSpan = dbSpans?.find(span => span.description?.includes('INSERT INTO logs'));
const auditLogsInsertSpan = dbSpans?.find(span => span.description?.includes('INSERT INTO audit_logs'));

expect(logsInsertSpan).toBeDefined();
expect(logsInsertSpan?.op).toBe('db.query');
expect(logsInsertSpan?.data?.['db.system.name']).toBe('sqlite');
expect(logsInsertSpan?.data?.['sentry.origin']).toBe('auto.db.nuxt');

expect(auditLogsInsertSpan).toBeDefined();
expect(auditLogsInsertSpan?.op).toBe('db.query');
expect(auditLogsInsertSpan?.data?.['db.system.name']).toBe('sqlite');
expect(auditLogsInsertSpan?.data?.['sentry.origin']).toBe('auto.db.nuxt');
});

test('creates correct span count for multiple database operations', async ({ request }) => {
const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction === 'GET /api/db-multi-test';
});

await request.get('/api/db-multi-test?method=multiple-dbs');

const transaction = await transactionPromise;

const dbSpans = transaction.spans?.filter(span => span.op === 'db.query');

// We should have multiple spans:
// - 3 CREATE TABLE (exec) spans
// - 3 INSERT (exec) spans
// - 3 SELECT (prepare + get) spans
// Total should be at least 9 spans
expect(dbSpans).toBeDefined();
expect(dbSpans!.length).toBeGreaterThanOrEqual(9);
});
});
Loading
Loading