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
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ export default defineNuxtConfig({
},
},
},
nitro: {
storage: {
'test-storage': {
driver: 'memory',
},
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useStorage } from '#imports';
import { defineEventHandler } from 'h3';

export default defineEventHandler(async _event => {
const storage = useStorage('test-storage');

// Test all alias methods (get, set, del, remove)
const results: Record<string, unknown> = {};

// Test set (alias for setItem)
await storage.set('alias:user', { name: 'Jane Doe', role: 'admin' });
results.set = 'success';

// Test get (alias for getItem)
const user = await storage.get('alias:user');
results.get = user;

// Test has (alias for hasItem)
const hasUser = await storage.has('alias:user');
results.has = hasUser;

// Setup for delete tests
await storage.set('alias:temp1', 'temp1');
await storage.set('alias:temp2', 'temp2');

// Test del (alias for removeItem)
await storage.del('alias:temp1');
results.del = 'success';

// Test remove (alias for removeItem)
await storage.remove('alias:temp2');
results.remove = 'success';

// Verify deletions worked
const hasTemp1 = await storage.has('alias:temp1');
const hasTemp2 = await storage.has('alias:temp2');
results.verifyDeletions = !hasTemp1 && !hasTemp2;

// Clean up
await storage.clear();

return {
success: true,
results,
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useStorage } from '#imports';
import { defineEventHandler } from 'h3';

export default defineEventHandler(async _event => {
const storage = useStorage('test-storage');

// Test all instrumented methods
const results: Record<string, unknown> = {};

// Test setItem
await storage.setItem('user:123', { name: 'John Doe', email: 'john@example.com' });
results.setItem = 'success';

// Test setItemRaw
await storage.setItemRaw('raw:data', Buffer.from('raw data'));
results.setItemRaw = 'success';

// Manually set batch items (setItems not supported by memory driver)
await storage.setItem('batch:1', 'value1');
await storage.setItem('batch:2', 'value2');

// Test hasItem
const hasUser = await storage.hasItem('user:123');
results.hasItem = hasUser;

// Test getItem
const user = await storage.getItem('user:123');
results.getItem = user;

// Test getItemRaw
const rawData = await storage.getItemRaw('raw:data');
results.getItemRaw = rawData?.toString();

// Test getKeys
const keys = await storage.getKeys('batch:');
results.getKeys = keys;

// Test removeItem
await storage.removeItem('batch:1');
results.removeItem = 'success';

// Test clear
await storage.clear();
results.clear = 'success';

// Verify clear worked
const keysAfterClear = await storage.getKeys();
results.keysAfterClear = keysAfterClear;

return {
success: true,
results,
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/nuxt';

test.describe('Storage Instrumentation - Aliases', () => {
const prefixKey = (key: string) => `test-storage:${key}`;
const SEMANTIC_ATTRIBUTE_CACHE_KEY = 'cache.key';
const SEMANTIC_ATTRIBUTE_CACHE_HIT = 'cache.hit';

test('instruments storage alias methods (get, set, has, del, remove) and creates spans', async ({ request }) => {
const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction?.includes('GET /api/storage-aliases-test') ?? false;
});

const response = await request.get('/api/storage-aliases-test');
expect(response.status()).toBe(200);

const transaction = await transactionPromise;

// Helper to find spans by operation
const findSpansByOp = (op: string) => {
return transaction.spans?.filter(span => span.data?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === op) || [];
};

// Test set (alias for setItem)
const setSpans = findSpansByOp('cache.set_item');
expect(setSpans.length).toBeGreaterThanOrEqual(1);
const setSpan = setSpans.find(span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('alias:user'));
expect(setSpan).toBeDefined();
expect(setSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.set_item',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('alias:user'),
'nuxt.storage.op': 'setItem',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});
expect(setSpan?.description).toBe(prefixKey('alias:user'));

// Test get (alias for getItem)
const getSpans = findSpansByOp('cache.get_item');
expect(getSpans.length).toBeGreaterThanOrEqual(1);
const getSpan = getSpans.find(span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('alias:user'));
expect(getSpan).toBeDefined();
expect(getSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.get_item',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('alias:user'),
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: true,
'nuxt.storage.op': 'getItem',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});
expect(getSpan?.description).toBe(prefixKey('alias:user'));

// Test has (alias for hasItem)
const hasSpans = findSpansByOp('cache.has_item');
expect(hasSpans.length).toBeGreaterThanOrEqual(1);
const hasSpan = hasSpans.find(span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('alias:user'));
expect(hasSpan).toBeDefined();
expect(hasSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.has_item',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('alias:user'),
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: true,
'nuxt.storage.op': 'hasItem',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});

// Test del and remove (both aliases for removeItem)
const removeSpans = findSpansByOp('cache.remove_item');
expect(removeSpans.length).toBeGreaterThanOrEqual(2); // Should have both del and remove calls

const delSpan = removeSpans.find(span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('alias:temp1'));
expect(delSpan).toBeDefined();
expect(delSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.remove_item',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('alias:temp1'),
'nuxt.storage.op': 'removeItem',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});
expect(delSpan?.description).toBe(prefixKey('alias:temp1'));

const removeSpan = removeSpans.find(span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('alias:temp2'));
expect(removeSpan).toBeDefined();
expect(removeSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.remove_item',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('alias:temp2'),
'nuxt.storage.op': 'removeItem',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});
expect(removeSpan?.description).toBe(prefixKey('alias:temp2'));

// Verify all spans have OK status
const allStorageSpans = transaction.spans?.filter(
span => span.data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.cache.nuxt',
);
expect(allStorageSpans?.length).toBeGreaterThan(0);
allStorageSpans?.forEach(span => {
expect(span.status).toBe('ok');
});
});
});
151 changes: 151 additions & 0 deletions dev-packages/e2e-tests/test-applications/nuxt-3/tests/storage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/nuxt';

test.describe('Storage Instrumentation', () => {
const prefixKey = (key: string) => `test-storage:${key}`;
const SEMANTIC_ATTRIBUTE_CACHE_KEY = 'cache.key';
const SEMANTIC_ATTRIBUTE_CACHE_HIT = 'cache.hit';

test('instruments all storage operations and creates spans with correct attributes', async ({ request }) => {
const transactionPromise = waitForTransaction('nuxt-3', transactionEvent => {
return transactionEvent.transaction?.includes('GET /api/storage-test') ?? false;
});

const response = await request.get('/api/storage-test');
expect(response.status()).toBe(200);

const transaction = await transactionPromise;

// Helper to find spans by operation
const findSpansByOp = (op: string) => {
return transaction.spans?.filter(span => span.data?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === op) || [];
};

// Test setItem spans
const setItemSpans = findSpansByOp('cache.set_item');
expect(setItemSpans.length).toBeGreaterThanOrEqual(1);
const setItemSpan = setItemSpans.find(span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('user:123'));
expect(setItemSpan).toBeDefined();
expect(setItemSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.set_item',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('user:123'),
'nuxt.storage.op': 'setItem',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});
expect(setItemSpan?.description).toBe(prefixKey('user:123'));

// Test setItemRaw spans
const setItemRawSpans = findSpansByOp('cache.set_item_raw');
expect(setItemRawSpans.length).toBeGreaterThanOrEqual(1);
const setItemRawSpan = setItemRawSpans.find(
span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('raw:data'),
);
expect(setItemRawSpan).toBeDefined();
expect(setItemRawSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.set_item_raw',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('raw:data'),
'nuxt.storage.op': 'setItemRaw',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});

// Test hasItem spans - should have cache hit attribute
const hasItemSpans = findSpansByOp('cache.has_item');
expect(hasItemSpans.length).toBeGreaterThanOrEqual(1);
const hasItemSpan = hasItemSpans.find(span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('user:123'));
expect(hasItemSpan).toBeDefined();
expect(hasItemSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.has_item',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('user:123'),
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: true,
'nuxt.storage.op': 'hasItem',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});

// Test getItem spans - should have cache hit attribute
const getItemSpans = findSpansByOp('cache.get_item');
expect(getItemSpans.length).toBeGreaterThanOrEqual(1);
const getItemSpan = getItemSpans.find(span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('user:123'));
expect(getItemSpan).toBeDefined();
expect(getItemSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.get_item',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('user:123'),
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: true,
'nuxt.storage.op': 'getItem',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});
expect(getItemSpan?.description).toBe(prefixKey('user:123'));

// Test getItemRaw spans - should have cache hit attribute
const getItemRawSpans = findSpansByOp('cache.get_item_raw');
expect(getItemRawSpans.length).toBeGreaterThanOrEqual(1);
const getItemRawSpan = getItemRawSpans.find(
span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('raw:data'),
);
expect(getItemRawSpan).toBeDefined();
expect(getItemRawSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.get_item_raw',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('raw:data'),
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: true,
'nuxt.storage.op': 'getItemRaw',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});

// Test getKeys spans
const getKeysSpans = findSpansByOp('cache.get_keys');
expect(getKeysSpans.length).toBeGreaterThanOrEqual(1);
expect(getKeysSpans[0]?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.get_keys',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
'nuxt.storage.op': 'getKeys',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});

// Test removeItem spans
const removeItemSpans = findSpansByOp('cache.remove_item');
expect(removeItemSpans.length).toBeGreaterThanOrEqual(1);
const removeItemSpan = removeItemSpans.find(
span => span.data?.[SEMANTIC_ATTRIBUTE_CACHE_KEY] === prefixKey('batch:1'),
);
expect(removeItemSpan).toBeDefined();
expect(removeItemSpan?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.remove_item',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: prefixKey('batch:1'),
'nuxt.storage.op': 'removeItem',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});

// Test clear spans
const clearSpans = findSpansByOp('cache.clear');
expect(clearSpans.length).toBeGreaterThanOrEqual(1);
expect(clearSpans[0]?.data).toMatchObject({
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.clear',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nuxt',
'nuxt.storage.op': 'clear',
'nuxt.storage.mount': 'test-storage:',
'nuxt.storage.driver': 'memory',
});

// Verify all spans have OK status
const allStorageSpans = transaction.spans?.filter(
span => span.data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.cache.nuxt',
);
expect(allStorageSpans?.length).toBeGreaterThan(0);
allStorageSpans?.forEach(span => {
expect(span.status).toBe('ok');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@ export default defineNuxtConfig({
},
},
},
nitro: {
storage: {
'test-storage': {
driver: 'memory',
},
},
},
});
Loading