Skip to content

Commit c2263ee

Browse files
committed
more tests & fixes
1 parent 8114951 commit c2263ee

File tree

7 files changed

+251
-2
lines changed

7 files changed

+251
-2
lines changed

dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-0/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Sentry.init({
55
dsn: 'https://[email protected]/1337',
66
release: '1.0',
77
transport: loggingTransport,
8-
tracesSampleRate: 1,
8+
tracesSampleRate: 0
99
});
1010

1111
import express from 'express';

dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-0/test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ afterAll(() => {
77

88
test('should capture and send Express controller error with txn name if tracesSampleRate is 0', async () => {
99
const runner = createRunner(__dirname, 'server.ts')
10-
.ignore('transaction')
1110
.expect({
1211
event: {
1312
exception: {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1,
8+
transport: loggingTransport,
9+
});
10+
11+
import express from 'express';
12+
13+
const app = express();
14+
15+
app.get('/test/express/:id', req => {
16+
throw new Error(`test_error with id ${req.params.id}`);
17+
});
18+
19+
Sentry.setupExpressErrorHandler(app);
20+
21+
startExpressServerAndSendPortToRunner(app);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { afterAll, expect, test } from 'vitest';
2+
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
3+
4+
afterAll(() => {
5+
cleanupChildProcesses();
6+
});
7+
8+
test('should capture and send Express controller error with txn name if tracesSampleRate is 1', async () => {
9+
const runner = createRunner(__dirname, 'server.ts')
10+
.expect({
11+
transaction: {
12+
transaction: 'GET /test/express/:id',
13+
},
14+
})
15+
.expect({
16+
event: {
17+
exception: {
18+
values: [
19+
{
20+
mechanism: {
21+
type: 'middleware',
22+
handled: false,
23+
},
24+
type: 'Error',
25+
value: 'test_error with id 123',
26+
stacktrace: {
27+
frames: expect.arrayContaining([
28+
expect.objectContaining({
29+
function: expect.any(String),
30+
lineno: expect.any(Number),
31+
colno: expect.any(Number),
32+
}),
33+
]),
34+
},
35+
},
36+
],
37+
},
38+
transaction: 'GET /test/express/:id',
39+
},
40+
})
41+
.start();
42+
runner.makeRequest('get', '/test/express/123', { expectError: true });
43+
await runner.completed();
44+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as Sentry from '@sentry/node';
2+
import { generateText } from 'ai';
3+
import { MockLanguageModelV1 } from 'ai/test';
4+
import { z } from 'zod';
5+
import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests';
6+
import express from 'express';
7+
8+
const app = express();
9+
10+
app.get('/test/error-in-tool', async (_req, res, next) => {
11+
Sentry.setTag('test-tag', 'test-value');
12+
13+
try {
14+
await generateText({
15+
model: new MockLanguageModelV1({
16+
doGenerate: async () => ({
17+
rawCall: { rawPrompt: null, rawSettings: {} },
18+
finishReason: 'tool-calls',
19+
usage: { promptTokens: 15, completionTokens: 25 },
20+
text: 'Tool call completed!',
21+
toolCalls: [
22+
{
23+
toolCallType: 'function',
24+
toolCallId: 'call-1',
25+
toolName: 'getWeather',
26+
args: '{ "location": "San Francisco" }',
27+
},
28+
],
29+
}),
30+
}),
31+
tools: {
32+
getWeather: {
33+
parameters: z.object({ location: z.string() }),
34+
execute: async () => {
35+
throw new Error('Error in tool');
36+
},
37+
},
38+
},
39+
prompt: 'What is the weather in San Francisco?',
40+
});
41+
} catch (error) {
42+
next(error);
43+
return;
44+
}
45+
46+
res.send({ message: 'OK' });
47+
});
48+
Sentry.setupExpressErrorHandler(app);
49+
50+
startExpressServerAndSendPortToRunner(app);

dev-packages/node-integration-tests/suites/tracing/vercelai/scenario-error-in-tool.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { MockLanguageModelV1 } from 'ai/test';
44
import { z } from 'zod';
55

66
async function run() {
7+
Sentry.setTag('test-tag', 'test-value');
8+
79
await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
810
await generateText({
911
model: new MockLanguageModelV1({

dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { afterAll, describe, expect } from 'vitest';
22
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
3+
import { Event } from '@sentry/node';
34

45
describe('Vercel AI integration', () => {
56
afterAll(() => {
@@ -485,6 +486,10 @@ describe('Vercel AI integration', () => {
485486
status: 'unknown_error',
486487
}),
487488
]),
489+
490+
tags: {
491+
'test-tag': 'test-value',
492+
},
488493
};
489494

490495
let traceId: string = 'unset-trace-id';
@@ -505,6 +510,9 @@ describe('Vercel AI integration', () => {
505510
}),
506511
]),
507512
},
513+
tags: {
514+
'test-tag': 'test-value',
515+
},
508516
};
509517

510518
await createRunner()
@@ -526,4 +534,129 @@ describe('Vercel AI integration', () => {
526534
.completed();
527535
});
528536
});
537+
538+
createEsmAndCjsTests(__dirname, 'scenario-error-in-tool-express.mjs', 'instrument.mjs', (createRunner, test) => {
539+
test('captures error in tool in express server', async () => {
540+
const expectedTransaction = {
541+
transaction: 'GET /test/error-in-tool',
542+
spans: expect.arrayContaining([
543+
expect.objectContaining({
544+
data: {
545+
'vercel.ai.model.id': 'mock-model-id',
546+
'vercel.ai.model.provider': 'mock-provider',
547+
'vercel.ai.operationId': 'ai.generateText',
548+
'vercel.ai.pipeline.name': 'generateText',
549+
'vercel.ai.settings.maxRetries': 2,
550+
'vercel.ai.settings.maxSteps': 1,
551+
'vercel.ai.streaming': false,
552+
'gen_ai.response.model': 'mock-model-id',
553+
'operation.name': 'ai.generateText',
554+
'sentry.op': 'gen_ai.invoke_agent',
555+
'sentry.origin': 'auto.vercelai.otel',
556+
},
557+
description: 'generateText',
558+
op: 'gen_ai.invoke_agent',
559+
origin: 'auto.vercelai.otel',
560+
status: 'unknown_error',
561+
}),
562+
expect.objectContaining({
563+
data: {
564+
'vercel.ai.model.id': 'mock-model-id',
565+
'vercel.ai.model.provider': 'mock-provider',
566+
'vercel.ai.operationId': 'ai.generateText.doGenerate',
567+
'vercel.ai.pipeline.name': 'generateText.doGenerate',
568+
'vercel.ai.response.finishReason': 'tool-calls',
569+
'vercel.ai.response.id': expect.any(String),
570+
'vercel.ai.response.model': 'mock-model-id',
571+
'vercel.ai.response.timestamp': expect.any(String),
572+
'vercel.ai.settings.maxRetries': 2,
573+
'vercel.ai.streaming': false,
574+
'gen_ai.request.model': 'mock-model-id',
575+
'gen_ai.response.finish_reasons': ['tool-calls'],
576+
'gen_ai.response.id': expect.any(String),
577+
'gen_ai.response.model': 'mock-model-id',
578+
'gen_ai.system': 'mock-provider',
579+
'gen_ai.usage.input_tokens': 15,
580+
'gen_ai.usage.output_tokens': 25,
581+
'gen_ai.usage.total_tokens': 40,
582+
'operation.name': 'ai.generateText.doGenerate',
583+
'sentry.op': 'gen_ai.generate_text',
584+
'sentry.origin': 'auto.vercelai.otel',
585+
},
586+
description: 'generate_text mock-model-id',
587+
op: 'gen_ai.generate_text',
588+
origin: 'auto.vercelai.otel',
589+
status: 'ok',
590+
}),
591+
expect.objectContaining({
592+
data: {
593+
'vercel.ai.operationId': 'ai.toolCall',
594+
'gen_ai.tool.call.id': 'call-1',
595+
'gen_ai.tool.name': 'getWeather',
596+
'gen_ai.tool.type': 'function',
597+
'operation.name': 'ai.toolCall',
598+
'sentry.op': 'gen_ai.execute_tool',
599+
'sentry.origin': 'auto.vercelai.otel',
600+
},
601+
description: 'execute_tool getWeather',
602+
op: 'gen_ai.execute_tool',
603+
origin: 'auto.vercelai.otel',
604+
status: 'unknown_error',
605+
}),
606+
]),
607+
608+
tags: {
609+
'test-tag': 'test-value',
610+
},
611+
};
612+
613+
const expectedError = {
614+
contexts: {
615+
trace: {
616+
span_id: expect.any(String),
617+
trace_id: expect.any(String),
618+
},
619+
},
620+
exception: {
621+
values: expect.arrayContaining([
622+
expect.objectContaining({
623+
type: 'AI_ToolExecutionError',
624+
value: 'Error executing tool getWeather: Error in tool',
625+
}),
626+
]),
627+
},
628+
tags: {
629+
'test-tag': 'test-value',
630+
},
631+
};
632+
633+
let transactionEvent: Event | undefined;
634+
let errorEvent: Event | undefined;
635+
636+
const runner = await createRunner()
637+
.expect({
638+
transaction: transaction => {
639+
transactionEvent = transaction;
640+
},
641+
})
642+
.expect({
643+
event: event => {
644+
errorEvent = event;
645+
},
646+
})
647+
.start();
648+
649+
await runner.makeRequest('get', '/test/error-in-tool', { expectError: true });
650+
await runner.completed();
651+
652+
expect(transactionEvent).toBeDefined();
653+
expect(errorEvent).toBeDefined();
654+
655+
expect(transactionEvent).toMatchObject(expectedTransaction);
656+
657+
expect(errorEvent).toMatchObject(expectedError);
658+
expect(errorEvent!.contexts!.trace!.trace_id).toBe(transactionEvent!.contexts!.trace!.trace_id);
659+
expect(errorEvent!.contexts!.trace!.span_id).toBe(transactionEvent!.contexts!.trace!.span_id);
660+
});
661+
});
529662
});

0 commit comments

Comments
 (0)