From e386883d1264f37c32b864d610c445e731d4d545 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 12 Oct 2026 15:53:23 +0200 Subject: [PATCH] test(node): Cover ioredis v6 in the diagnostics_channel suite ioredis 6.0.0 keeps the ioredis:command and ioredis:connect tracing channels and their payload shape, so redisIntegration already works with it. Add an ioredis-6 alias and run the existing expectations against both v5.11 and v6 so a future payload change is caught in CI. Co-Authored-By: Claude Fable 5.1 --- .../node-integration-tests/package.json | 1 + .../ioredis-dc/scenario-ioredis-5-11.mjs | 4 +- .../tracing/ioredis-dc/scenario-ioredis-6.mjs | 33 +++++ .../suites/tracing/ioredis-dc/test.ts | 134 ++++++++++-------- yarn.lock | 17 +++ 5 files changed, 124 insertions(+), 65 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/ioredis-dc/scenario-ioredis-6.mjs diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index a8a0a474448a..afa12af3561c 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -77,6 +77,7 @@ "http-terminator": "^3.2.0", "ioredis": "5.10.1", "ioredis-5": "npm:ioredis@^5.11.0", + "ioredis-6": "npm:ioredis@^6.0.0", "kafkajs": "2.2.4", "knex": "^2.5.1", "koa": "^2.15.2", diff --git a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/scenario-ioredis-5-11.mjs b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/scenario-ioredis-5-11.mjs index 499bff7fb206..402adb6f364f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/scenario-ioredis-5-11.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/scenario-ioredis-5-11.mjs @@ -8,8 +8,8 @@ async function run() { await Sentry.startSpan( { - name: 'Test Span IORedis 5.11 DC', - op: 'test-span-ioredis-5-11-dc', + name: 'Test Span IORedis DC', + op: 'test-span-ioredis-dc', }, async () => { try { diff --git a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/scenario-ioredis-6.mjs b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/scenario-ioredis-6.mjs new file mode 100644 index 000000000000..994c679d9d3a --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/scenario-ioredis-6.mjs @@ -0,0 +1,33 @@ +import * as Sentry from '@sentry/node'; + +async function run() { + const { default: Redis } = await import('ioredis-6'); + const redisClient = new Redis({ host: '127.0.0.1', port: 6382, lazyConnect: true }); + + await redisClient.connect(); + + await Sentry.startSpan( + { + name: 'Test Span IORedis DC', + op: 'test-span-ioredis-dc', + }, + async () => { + try { + await redisClient.set('dc-test-key', 'test-value'); + await redisClient.set('dc-cache:test-key', 'test-value'); + + await redisClient.set('dc-cache:test-key-ex', 'test-value', 'EX', 10); + + await redisClient.get('dc-test-key'); + await redisClient.get('dc-cache:test-key'); + await redisClient.get('dc-cache:unavailable-data'); + + await redisClient.mget('dc-test-key', 'dc-cache:test-key', 'dc-cache:unavailable-data'); + } finally { + await redisClient.disconnect(); + } + }, + ); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts index d854e0386637..71db70a5425a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts @@ -27,7 +27,7 @@ import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; describeWithDockerCompose( - 'ioredis v5.11 diagnostics_channel auto instrumentation', + 'ioredis >=5.11 diagnostics_channel auto instrumentation', { workingDirectory: [__dirname] }, () => { afterAll(() => { @@ -35,7 +35,7 @@ describeWithDockerCompose( }); const ORIGIN = 'auto.db.redis.diagnostic_channel'; - const SEGMENT_NAME = 'Test Span IORedis 5.11 DC'; + const SEGMENT_NAME = 'Test Span IORedis DC'; const HOST = '127.0.0.1'; const PORT = 6382; @@ -89,69 +89,77 @@ describeWithDockerCompose( attributes: Record, ): unknown => streamedSpan(op, op, { ...PEER, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); - createEsmAndCjsTests(__dirname, 'scenario-ioredis-5-11.mjs', 'instrument.mjs', (createTestRunner, test) => { - test('creates spans for ioredis v5.11 commands via diagnostics_channel', { timeout: 75_000 }, async () => { - await createTestRunner() - .expect({ - span: container => { - // The connect span opens its own segment but shares the trace with the test span, - // so both segments arrive in the same container. - expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ - 'redis-connect', - SEGMENT_NAME, - ]); + const clients = [ + { label: 'v5.11', scenario: 'scenario-ioredis-5-11.mjs' }, + // v6 defaults to RESP3 and adds a `hello` handshake; the channel payload is unchanged. + { label: 'v6', scenario: 'scenario-ioredis-6.mjs' }, + ]; - // ioredis' own handshake commands (`client SETINFO`, `info`) are emitted on the - // channel too, but belong to the connect segment — the test span's children are the - // commands the scenario issues. - const spans = container.items.filter( - item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME, - ); + for (const { label, scenario } of clients) { + createEsmAndCjsTests(__dirname, scenario, 'instrument.mjs', (createTestRunner, test) => { + test(`creates spans for ioredis ${label} commands via diagnostics_channel`, { timeout: 75_000 }, async () => { + await createTestRunner() + .expect({ + span: container => { + // The connect span opens its own segment but shares the trace with the test span, + // so both segments arrive in the same container. + expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ + 'redis-connect', + SEGMENT_NAME, + ]); - expect(spans).toEqual([ - streamedSpan(`set ${HOST}:${PORT}`, DB_QUERY, { - [DB_OPERATION_NAME]: 'set', - [DB_QUERY_TEXT]: 'set dc-test-key ?', - }), - cacheSpan(CACHE_PUT, { - [DB_OPERATION_NAME]: 'set', - [DB_QUERY_TEXT]: 'set dc-cache:test-key ?', - [CACHE_KEY]: ['dc-cache:test-key'], - [CACHE_ITEM_SIZE]: 2, - }), - cacheSpan(CACHE_PUT, { - [DB_OPERATION_NAME]: 'set', - [DB_QUERY_TEXT]: 'set dc-cache:test-key-ex ? ? ?', - [CACHE_KEY]: ['dc-cache:test-key-ex'], - [CACHE_ITEM_SIZE]: 2, - }), - streamedSpan(`get ${HOST}:${PORT}`, DB_QUERY, { - [DB_OPERATION_NAME]: 'get', - [DB_QUERY_TEXT]: 'get dc-test-key', - }), - cacheSpan(CACHE_GET, { - [DB_OPERATION_NAME]: 'get', - [DB_QUERY_TEXT]: 'get dc-cache:test-key', - [CACHE_KEY]: ['dc-cache:test-key'], - [CACHE_HIT]: true, - [CACHE_ITEM_SIZE]: 10, - }), - cacheSpan(CACHE_GET, { - [DB_OPERATION_NAME]: 'get', - [DB_QUERY_TEXT]: 'get dc-cache:unavailable-data', - [CACHE_KEY]: ['dc-cache:unavailable-data'], - [CACHE_HIT]: false, - }), - streamedSpan(`mget ${HOST}:${PORT}`, DB_QUERY, { - [DB_OPERATION_NAME]: 'mget', - [DB_QUERY_TEXT]: 'mget ? ? ?', - }), - ]); - }, - }) - .start() - .completed(); + // ioredis' own handshake commands (`client SETINFO`, `info`) are emitted on the + // channel too, but belong to the connect segment — the test span's children are the + // commands the scenario issues. + const spans = container.items.filter( + item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME, + ); + + expect(spans).toEqual([ + streamedSpan(`set ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set dc-test-key ?', + }), + cacheSpan(CACHE_PUT, { + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set dc-cache:test-key ?', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_ITEM_SIZE]: 2, + }), + cacheSpan(CACHE_PUT, { + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set dc-cache:test-key-ex ? ? ?', + [CACHE_KEY]: ['dc-cache:test-key-ex'], + [CACHE_ITEM_SIZE]: 2, + }), + streamedSpan(`get ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get dc-test-key', + }), + cacheSpan(CACHE_GET, { + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get dc-cache:test-key', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 10, + }), + cacheSpan(CACHE_GET, { + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get dc-cache:unavailable-data', + [CACHE_KEY]: ['dc-cache:unavailable-data'], + [CACHE_HIT]: false, + }), + streamedSpan(`mget ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'mget', + [DB_QUERY_TEXT]: 'mget ? ? ?', + }), + ]); + }, + }) + .start() + .completed(); + }); }); - }); + } }, ); diff --git a/yarn.lock b/yarn.lock index 0fb72c08a93a..eb95b20a3424 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5334,6 +5334,11 @@ resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.5.1.tgz#a0a3449993b10c7aeb91ecb0d5f1a23692297e51" integrity sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw== +"@ioredis/commands@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-2.0.0.tgz#7207c56e0be51fd9f2ec2505ebdb9372a3ceb76d" + integrity sha512-vrx0AE/T0h7cRZwfo1M39Cr+ZhZrkf0V8mQN75wucKCxCLD9l/VX6no3gFvrLqD1IlG/1LtzWovqEw3t0Vr9zg== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -17637,6 +17642,18 @@ invariant@^2.2.1, invariant@^2.2.2: redis-parser "3.0.0" standard-as-callback "2.1.0" +"ioredis-6@npm:ioredis@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-6.0.0.tgz#b757c590547c43a5e18ea9c312d05c5c46c7e6fa" + integrity sha512-f+Dtubxfpf6KYFq7WVXJoOLn0bk4TJrMrN9SzeE+jrWrCWj7XX3fA6vkryafhADX+GMymRxgDJDOI33COkJc0w== + dependencies: + "@ioredis/commands" "2.0.0" + cluster-key-slot "1.1.1" + debug "4.4.3" + denque "2.1.0" + redis-errors "1.2.0" + standard-as-callback "2.1.0" + ioredis@5.10.1, ioredis@^5.10.1: version "5.10.1" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.10.1.tgz#6082781d8aec8d51ee4936bf81d0610404db1e3d"