From 92b34bc435014b8a2f7338a8f35fbe73cabb1af9 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 2 Jul 2026 08:35:54 +0200 Subject: [PATCH] test(google-genai): Port fake scenarios to the real SDK Replaces the hand-written MockGoogleGenerativeAI classes in the truncation and system-instructions scenarios with the real @google/genai SDK driven by an express mock server, matching the rest of the suite. The auto integration already wraps clients via the same instrumentGoogleGenAIClient the fakes used, so dropping the manual wrap is behavior-preserving. Adds the express-server transaction filter to instrument-no-truncation.mjs (the fakes made no HTTP calls, so it wasn't needed before). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../google-genai/instrument-no-truncation.mjs | 7 ++ .../scenario-message-truncation.mjs | 64 +++++++++--------- .../google-genai/scenario-no-truncation.mjs | 60 ++++++++++------- .../scenario-system-instructions.mjs | 67 ++++++++++--------- 4 files changed, 106 insertions(+), 92 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-no-truncation.mjs index 16eb0a6d74c6..5daa0a199d49 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-no-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-no-truncation.mjs @@ -14,5 +14,12 @@ Sentry.init({ enableTruncation: false, }), ], + beforeSendTransaction: event => { + // Filter out mock express server transactions + if (event.transaction.includes('/v1beta/')) { + return null; + } + return event; + }, streamGenAiSpans: true, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-message-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-message-truncation.mjs index e6146214d7fe..d3cb34648f4a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-message-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-message-truncation.mjs @@ -1,48 +1,40 @@ -import { instrumentGoogleGenAIClient } from '@sentry/core'; +import { GoogleGenAI } from '@google/genai'; import * as Sentry from '@sentry/node'; +import express from 'express'; -class MockGoogleGenerativeAI { - constructor(config) { - this.apiKey = config.apiKey; +function startMockGoogleGenAIServer() { + const app = express(); + app.use(express.json({ limit: '10mb' })); - this.models = { - generateContent: this._generateContent.bind(this), - }; - } - - async _generateContent() { - await new Promise(resolve => setTimeout(resolve, 10)); - - return { - response: { - text: () => 'Response to truncated messages', - usageMetadata: { - promptTokenCount: 10, - candidatesTokenCount: 15, - totalTokenCount: 25, + app.post('/v1beta/models/:model\\:generateContent', (req, res) => { + res.send({ + candidates: [ + { + content: { parts: [{ text: 'Response to truncated messages' }], role: 'model' }, + finishReason: 'stop', + index: 0, }, - candidates: [ - { - content: { - parts: [{ text: 'Response to truncated messages' }], - role: 'model', - }, - finishReason: 'STOP', - }, - ], - }, - }; - } + ], + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 15, totalTokenCount: 25 }, + }); + }); + + return new Promise(resolve => { + const server = app.listen(0, () => { + resolve(server); + }); + }); } async function run() { + const server = await startMockGoogleGenAIServer(); + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { - const mockClient = new MockGoogleGenerativeAI({ + const client = new GoogleGenAI({ apiKey: 'mock-api-key', + httpOptions: { baseUrl: `http://localhost:${server.address().port}` }, }); - const client = instrumentGoogleGenAIClient(mockClient, { enableTruncation: true, recordInputs: true }); - // Test 1: Given an array of messages only the last message should be kept // The last message should be truncated to fit within the 20KB limit const largeContent1 = 'A'.repeat(15000); // ~15KB @@ -80,6 +72,10 @@ async function run() { ], }); }); + + await Sentry.flush(2000); + + server.close(); } run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-no-truncation.mjs index 13b271a23878..67ece6759577 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-no-truncation.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-no-truncation.mjs @@ -1,35 +1,39 @@ -import { instrumentGoogleGenAIClient } from '@sentry/core'; +import { GoogleGenAI } from '@google/genai'; import * as Sentry from '@sentry/node'; +import express from 'express'; -class MockGoogleGenerativeAI { - constructor(config) { - this.apiKey = config.apiKey; - this.models = { - generateContent: this._generateContent.bind(this), - }; - } - - async _generateContent() { - await new Promise(resolve => setTimeout(resolve, 10)); - return { - response: { - text: () => 'Response', - usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5, totalTokenCount: 15 }, - candidates: [ - { - content: { parts: [{ text: 'Response' }], role: 'model' }, - finishReason: 'STOP', - }, - ], - }, - }; - } +function startMockGoogleGenAIServer() { + const app = express(); + app.use(express.json({ limit: '10mb' })); + + app.post('/v1beta/models/:model\\:generateContent', (req, res) => { + res.send({ + candidates: [ + { + content: { parts: [{ text: 'Response' }], role: 'model' }, + finishReason: 'stop', + index: 0, + }, + ], + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5, totalTokenCount: 15 }, + }); + }); + + return new Promise(resolve => { + const server = app.listen(0, () => { + resolve(server); + }); + }); } async function run() { + const server = await startMockGoogleGenAIServer(); + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { - const mockClient = new MockGoogleGenerativeAI({ apiKey: 'mock-api-key' }); - const client = instrumentGoogleGenAIClient(mockClient, { enableTruncation: false, recordInputs: true }); + const client = new GoogleGenAI({ + apiKey: 'mock-api-key', + httpOptions: { baseUrl: `http://localhost:${server.address().port}` }, + }); // Long content that would normally be truncated const longContent = 'A'.repeat(50_000); @@ -42,6 +46,10 @@ async function run() { ], }); }); + + await Sentry.flush(2000); + + server.close(); } run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-system-instructions.mjs b/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-system-instructions.mjs index d4081d052968..1a6f7d81d49e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-system-instructions.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-system-instructions.mjs @@ -1,41 +1,40 @@ -import { instrumentGoogleGenAIClient } from '@sentry/core'; +import { GoogleGenAI } from '@google/genai'; import * as Sentry from '@sentry/node'; +import express from 'express'; -class MockGoogleGenAI { - constructor(config) { - this.apiKey = config.apiKey; - this.models = { - generateContent: async params => { - await new Promise(resolve => setTimeout(resolve, 10)); - return { - response: { - text: () => 'Response', - modelVersion: params.model, - usageMetadata: { - promptTokenCount: 10, - candidatesTokenCount: 5, - totalTokenCount: 15, - }, - candidates: [ - { - content: { - parts: [{ text: 'Response' }], - role: 'model', - }, - finishReason: 'STOP', - }, - ], - }, - }; - }, - }; - } +function startMockGoogleGenAIServer() { + const app = express(); + app.use(express.json()); + + app.post('/v1beta/models/:model\\:generateContent', (req, res) => { + res.send({ + candidates: [ + { + content: { parts: [{ text: 'Response' }], role: 'model' }, + finishReason: 'stop', + index: 0, + }, + ], + modelVersion: req.params.model, + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5, totalTokenCount: 15 }, + }); + }); + + return new Promise(resolve => { + const server = app.listen(0, () => { + resolve(server); + }); + }); } async function run() { + const server = await startMockGoogleGenAIServer(); + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { - const mockClient = new MockGoogleGenAI({ apiKey: 'mock-api-key' }); - const client = instrumentGoogleGenAIClient(mockClient); + const client = new GoogleGenAI({ + apiKey: 'mock-api-key', + httpOptions: { baseUrl: `http://localhost:${server.address().port}` }, + }); await client.models.generateContent({ model: 'gemini-1.5-flash', @@ -45,6 +44,10 @@ async function run() { contents: [{ role: 'user', parts: [{ text: 'Hello' }] }], }); }); + + await Sentry.flush(2000); + + server.close(); } run();