@@ -18,7 +18,12 @@ import { getTestSpans } from '@opentelemetry/contrib-test-utils';
1818import './load-instrumentation' ;
1919
2020import { AttributeNames } from '../src/enums' ;
21- import { DescribeStreamCommand , KinesisClient } from '@aws-sdk/client-kinesis' ;
21+ import {
22+ DescribeStreamCommand ,
23+ KinesisClient ,
24+ PutRecordCommand ,
25+ PutRecordsCommand ,
26+ } from '@aws-sdk/client-kinesis' ;
2227import { NodeHttpHandler } from '@smithy/node-http-handler' ;
2328import * as fs from 'fs' ;
2429import * as nock from 'nock' ;
@@ -30,10 +35,18 @@ import { expect } from 'expect';
3035const region = 'us-east-1' ;
3136
3237describe ( 'Kinesis - v3' , ( ) => {
38+ let client : KinesisClient ;
39+ const dummyStreamName = 'dummy-stream-name' ;
40+
41+ beforeEach ( ( ) => {
42+ client = new KinesisClient ( {
43+ region : region ,
44+ requestHandler : new NodeHttpHandler ( ) ,
45+ } ) ;
46+ } ) ;
47+
3348 describe ( 'DescribeStream' , ( ) => {
3449 it ( 'Request span attributes - adds Stream Name' , async ( ) => {
35- const dummyStreamName = 'dummy-stream-name' ;
36-
3750 nock ( `https://kinesis.${ region } .amazonaws.com` )
3851 . post ( '/' )
3952 . reply (
@@ -48,11 +61,6 @@ describe('Kinesis - v3', () => {
4861 StreamName : dummyStreamName ,
4962 } ;
5063
51- // Use NodeHttpHandler to use HTTP instead of HTTP2 because nock does not support HTTP2
52- const client = new KinesisClient ( {
53- region : region ,
54- requestHandler : new NodeHttpHandler ( ) ,
55- } ) ;
5664 await client . send ( new DescribeStreamCommand ( params ) ) ;
5765
5866 const testSpans : ReadableSpan [ ] = getTestSpans ( ) ;
@@ -69,4 +77,199 @@ describe('Kinesis - v3', () => {
6977 expect ( describeSpan . kind ) . toBe ( SpanKind . CLIENT ) ;
7078 } ) ;
7179 } ) ;
80+
81+ describe ( 'PutRecord' , ( ) => {
82+ it ( 'injects trace context into JSON Data' , async ( ) => {
83+ nock ( `https://kinesis.${ region } .amazonaws.com` )
84+ . post ( '/' )
85+ . reply (
86+ 200 ,
87+ fs . readFileSync (
88+ './test/mock-responses/kinesis-put-record.json' ,
89+ 'utf8'
90+ )
91+ ) ;
92+
93+ const payload = { message : 'hello' } ;
94+ const params = {
95+ StreamName : dummyStreamName ,
96+ PartitionKey : 'pk-1' ,
97+ Data : new TextEncoder ( ) . encode ( JSON . stringify ( payload ) ) ,
98+ } ;
99+
100+ await client . send ( new PutRecordCommand ( params ) ) ;
101+
102+ const testSpans : ReadableSpan [ ] = getTestSpans ( ) ;
103+ const putRecordSpans : ReadableSpan [ ] = testSpans . filter (
104+ ( s : ReadableSpan ) => {
105+ return s . name === `${ dummyStreamName } send` ;
106+ }
107+ ) ;
108+ expect ( putRecordSpans . length ) . toBe ( 1 ) ;
109+ const span = putRecordSpans [ 0 ] ;
110+ expect ( span . kind ) . toBe ( SpanKind . PRODUCER ) ;
111+ expect (
112+ span . attributes [ AttributeNames . AWS_KINESIS_STREAM_NAME ]
113+ ) . toBe ( dummyStreamName ) ;
114+ expect ( span . attributes [ 'messaging.system' ] ) . toBe ( 'aws_kinesis' ) ;
115+ expect ( span . attributes [ 'messaging.destination.name' ] ) . toBe (
116+ dummyStreamName
117+ ) ;
118+
119+ // Verify trace context was injected into the Data payload
120+ const injectedData = JSON . parse (
121+ new TextDecoder ( ) . decode ( params . Data )
122+ ) ;
123+ expect ( injectedData . message ) . toBe ( 'hello' ) ;
124+ expect ( injectedData . traceparent ) . toBeDefined ( ) ;
125+ expect ( typeof injectedData . traceparent ) . toBe ( 'string' ) ;
126+ } ) ;
127+
128+ it ( 'extracts stream name from StreamARN' , async ( ) => {
129+ const endpoint = `https://kinesis.${ region } .amazonaws.com` ;
130+ nock ( endpoint )
131+ . post ( '/' )
132+ . reply (
133+ 200 ,
134+ fs . readFileSync (
135+ './test/mock-responses/kinesis-put-record.json' ,
136+ 'utf8'
137+ )
138+ ) ;
139+
140+ const arnClient = new KinesisClient ( {
141+ region : region ,
142+ requestHandler : new NodeHttpHandler ( ) ,
143+ endpoint : endpoint ,
144+ } ) ;
145+
146+ const payload = { message : 'hello' } ;
147+ const params = {
148+ StreamARN : `arn:aws:kinesis:${ region } :123456789012:stream/my-stream-from-arn` ,
149+ PartitionKey : 'pk-1' ,
150+ Data : new TextEncoder ( ) . encode ( JSON . stringify ( payload ) ) ,
151+ } ;
152+
153+ await arnClient . send ( new PutRecordCommand ( params ) ) ;
154+
155+ const testSpans : ReadableSpan [ ] = getTestSpans ( ) ;
156+ const putRecordSpans : ReadableSpan [ ] = testSpans . filter (
157+ ( s : ReadableSpan ) => {
158+ return s . name === 'my-stream-from-arn send' ;
159+ }
160+ ) ;
161+ expect ( putRecordSpans . length ) . toBe ( 1 ) ;
162+ const span = putRecordSpans [ 0 ] ;
163+ expect ( span . kind ) . toBe ( SpanKind . PRODUCER ) ;
164+ expect (
165+ span . attributes [ AttributeNames . AWS_KINESIS_STREAM_NAME ]
166+ ) . toBe ( 'my-stream-from-arn' ) ;
167+ expect ( span . attributes [ 'messaging.destination.name' ] ) . toBe (
168+ 'my-stream-from-arn'
169+ ) ;
170+ } ) ;
171+
172+ it ( 'handles non-JSON data gracefully' , async ( ) => {
173+ nock ( `https://kinesis.${ region } .amazonaws.com` )
174+ . post ( '/' )
175+ . reply (
176+ 200 ,
177+ fs . readFileSync (
178+ './test/mock-responses/kinesis-put-record.json' ,
179+ 'utf8'
180+ )
181+ ) ;
182+
183+ const nonJsonData = 'this is not json' ;
184+ const params = {
185+ StreamName : dummyStreamName ,
186+ PartitionKey : 'pk-1' ,
187+ Data : new TextEncoder ( ) . encode ( nonJsonData ) ,
188+ } ;
189+
190+ await client . send ( new PutRecordCommand ( params ) ) ;
191+
192+ const testSpans : ReadableSpan [ ] = getTestSpans ( ) ;
193+ const putRecordSpans : ReadableSpan [ ] = testSpans . filter (
194+ ( s : ReadableSpan ) => {
195+ return s . name === `${ dummyStreamName } send` ;
196+ }
197+ ) ;
198+ expect ( putRecordSpans . length ) . toBe ( 1 ) ;
199+ const span = putRecordSpans [ 0 ] ;
200+ expect ( span . kind ) . toBe ( SpanKind . PRODUCER ) ;
201+
202+ // Data should remain unchanged since it's not valid JSON
203+ const resultData = new TextDecoder ( ) . decode ( params . Data ) ;
204+ expect ( resultData ) . toBe ( nonJsonData ) ;
205+ } ) ;
206+ } ) ;
207+
208+ describe ( 'PutRecords' , ( ) => {
209+ it ( 'injects trace context into all records' , async ( ) => {
210+ nock ( `https://kinesis.${ region } .amazonaws.com` )
211+ . post ( '/' )
212+ . reply (
213+ 200 ,
214+ fs . readFileSync (
215+ './test/mock-responses/kinesis-put-records.json' ,
216+ 'utf8'
217+ )
218+ ) ;
219+
220+ const records = [
221+ {
222+ Data : new TextEncoder ( ) . encode ( JSON . stringify ( { id : 1 } ) ) ,
223+ PartitionKey : 'pk-1' ,
224+ } ,
225+ {
226+ Data : new TextEncoder ( ) . encode ( JSON . stringify ( { id : 2 } ) ) ,
227+ PartitionKey : 'pk-2' ,
228+ } ,
229+ ] ;
230+
231+ const params = {
232+ StreamName : dummyStreamName ,
233+ Records : records ,
234+ } ;
235+
236+ await client . send ( new PutRecordsCommand ( params ) ) ;
237+
238+ const testSpans : ReadableSpan [ ] = getTestSpans ( ) ;
239+ const putRecordsSpans : ReadableSpan [ ] = testSpans . filter (
240+ ( s : ReadableSpan ) => {
241+ return s . name === `${ dummyStreamName } send` ;
242+ }
243+ ) ;
244+ expect ( putRecordsSpans . length ) . toBe ( 1 ) ;
245+ const span = putRecordsSpans [ 0 ] ;
246+ expect ( span . kind ) . toBe ( SpanKind . PRODUCER ) ;
247+ expect (
248+ span . attributes [ AttributeNames . AWS_KINESIS_STREAM_NAME ]
249+ ) . toBe ( dummyStreamName ) ;
250+ expect ( span . attributes [ 'messaging.system' ] ) . toBe ( 'aws_kinesis' ) ;
251+ expect ( span . attributes [ 'messaging.destination.name' ] ) . toBe (
252+ dummyStreamName
253+ ) ;
254+
255+ // Verify trace context was injected into all records
256+ for ( const record of records ) {
257+ const injectedData = JSON . parse (
258+ new TextDecoder ( ) . decode ( record . Data )
259+ ) ;
260+ expect ( injectedData . traceparent ) . toBeDefined ( ) ;
261+ expect ( typeof injectedData . traceparent ) . toBe ( 'string' ) ;
262+ }
263+
264+ // Verify original data is preserved
265+ const firstRecord = JSON . parse (
266+ new TextDecoder ( ) . decode ( records [ 0 ] . Data )
267+ ) ;
268+ expect ( firstRecord . id ) . toBe ( 1 ) ;
269+ const secondRecord = JSON . parse (
270+ new TextDecoder ( ) . decode ( records [ 1 ] . Data )
271+ ) ;
272+ expect ( secondRecord . id ) . toBe ( 2 ) ;
273+ } ) ;
274+ } ) ;
72275} ) ;
0 commit comments