|
| 1 | +import { TracerWrapper } from "../../tracer-wrapper"; |
| 2 | +import { EventBridgeSNSEventTraceExtractor } from "./event-bridge-sns"; |
| 3 | +import { StepFunctionContextService } from "../../step-function-service"; |
| 4 | +import { SNSEvent } from "aws-lambda"; |
| 5 | + |
| 6 | +let mockSpanContext: any = null; |
| 7 | + |
| 8 | +// Mocking extract is needed, due to dd-trace being a No-op |
| 9 | +// if the detected environment is testing. This is expected, since |
| 10 | +// we don't want to test dd-trace extraction, but our components. |
| 11 | +const ddTrace = require("dd-trace"); |
| 12 | +jest.mock("dd-trace", () => { |
| 13 | + return { |
| 14 | + ...ddTrace, |
| 15 | + _tracer: { _service: {} }, |
| 16 | + extract: (_carrier: any, _headers: any) => mockSpanContext, |
| 17 | + }; |
| 18 | +}); |
| 19 | +const spyTracerWrapper = jest.spyOn(TracerWrapper.prototype, "extract"); |
| 20 | + |
| 21 | +describe("EventBridgeSNSEventTraceExtractor", () => { |
| 22 | + describe("extract", () => { |
| 23 | + beforeEach(() => { |
| 24 | + mockSpanContext = null; |
| 25 | + }); |
| 26 | + |
| 27 | + afterEach(() => { |
| 28 | + jest.resetModules(); |
| 29 | + }); |
| 30 | + |
| 31 | + it("extracts trace context with valid payload", () => { |
| 32 | + mockSpanContext = { |
| 33 | + toTraceId: () => "1234567890123456789", |
| 34 | + toSpanId: () => "9876543210987654321", |
| 35 | + _sampling: { |
| 36 | + priority: "1", |
| 37 | + }, |
| 38 | + }; |
| 39 | + const tracerWrapper = new TracerWrapper(); |
| 40 | + |
| 41 | + const payload = { |
| 42 | + Records: [ |
| 43 | + { |
| 44 | + EventSource: "aws:sns", |
| 45 | + EventVersion: "1.0", |
| 46 | + EventSubscriptionArn: "arn:aws:sns:us-east-1:123456123456:my-topic:12345678-1234-1234-1234-123456789012", |
| 47 | + Sns: { |
| 48 | + Type: "Notification", |
| 49 | + MessageId: "12345678-1234-1234-1234-123456789012", |
| 50 | + TopicArn: "arn:aws:sns:us-east-1:123456123456:my-topic", |
| 51 | + Message: '{"version":"0","id":"12345678-1234-1234-1234-123456789012","detail-type":"my.Detail","source":"my.Source","account":"123456123456","time":"2023-08-03T22:49:03Z","region":"us-east-1","resources":[],"detail":{"text":"Hello, world!","_datadog":{"x-datadog-trace-id":"1234567890123456789","x-datadog-parent-id":"9876543210987654321","x-datadog-sampling-priority":"1","x-datadog-tags":"_dd.p.dm=-0"}}}', |
| 52 | + Timestamp: "2023-08-03T22:49:03.123Z", |
| 53 | + SignatureVersion: "1", |
| 54 | + Signature: "EXAMPLE", |
| 55 | + SigningCertUrl: "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-123456789012.pem", |
| 56 | + Subject: undefined, |
| 57 | + UnsubscribeUrl: "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456123456:my-topic:12345678-1234-1234-1234-123456789012", |
| 58 | + MessageAttributes: {} |
| 59 | + } |
| 60 | + } |
| 61 | + ] |
| 62 | + }; |
| 63 | + |
| 64 | + const extractor = new EventBridgeSNSEventTraceExtractor(tracerWrapper); |
| 65 | + |
| 66 | + const traceContext = extractor.extract(payload); |
| 67 | + expect(traceContext).not.toBeNull(); |
| 68 | + |
| 69 | + expect(spyTracerWrapper).toHaveBeenCalledWith({ |
| 70 | + "x-datadog-parent-id": "9876543210987654321", |
| 71 | + "x-datadog-sampling-priority": "1", |
| 72 | + "x-datadog-tags": "_dd.p.dm=-0", |
| 73 | + "x-datadog-trace-id": "1234567890123456789", |
| 74 | + }); |
| 75 | + |
| 76 | + expect(traceContext?.toTraceId()).toBe("1234567890123456789"); |
| 77 | + expect(traceContext?.toSpanId()).toBe("9876543210987654321"); |
| 78 | + expect(traceContext?.sampleMode()).toBe("1"); |
| 79 | + expect(traceContext?.source).toBe("event"); |
| 80 | + }); |
| 81 | + |
| 82 | + it.each([ |
| 83 | + ["Records", {}], |
| 84 | + ["Records first entry", { Records: [] }], |
| 85 | + ["Records first entry Sns", { Records: [{}] }], |
| 86 | + ["Records first entry Sns Message", { Records: [{ Sns: {} }] }], |
| 87 | + ["valid JSON in Message", { Records: [{ Sns: { Message: "{" } }] }], // JSON.parse should fail |
| 88 | + ["detail in Message", { Records: [{ Sns: { Message: "{}" } }] }], |
| 89 | + ["_datadog in detail", { Records: [{ Sns: { Message: '{"detail":{"text":"Hello, world!"}}' } }] }], |
| 90 | + ])("returns null and skips extracting when payload is missing '%s'", (_, payload) => { |
| 91 | + const tracerWrapper = new TracerWrapper(); |
| 92 | + const extractor = new EventBridgeSNSEventTraceExtractor(tracerWrapper); |
| 93 | + |
| 94 | + const traceContext = extractor.extract(payload as any); |
| 95 | + expect(traceContext).toBeNull(); |
| 96 | + }); |
| 97 | + |
| 98 | + it("returns null when extracted span context by tracer is null", () => { |
| 99 | + const tracerWrapper = new TracerWrapper(); |
| 100 | + |
| 101 | + const payload = { |
| 102 | + Records: [ |
| 103 | + { |
| 104 | + EventSource: "aws:sns", |
| 105 | + EventVersion: "1.0", |
| 106 | + EventSubscriptionArn: "arn:aws:sns:us-east-1:123456123456:my-topic:12345678-1234-1234-1234-123456789012", |
| 107 | + Sns: { |
| 108 | + Type: "Notification", |
| 109 | + MessageId: "12345678-1234-1234-1234-123456789012", |
| 110 | + TopicArn: "arn:aws:sns:us-east-1:123456123456:my-topic", |
| 111 | + // Message is missing _datadog |
| 112 | + Message: '{"version":"0","id":"12345678-1234-1234-1234-123456789012","detail-type":"my.Detail","source":"my.Source","account":"123456123456","time":"2023-08-03T22:49:03Z","region":"us-east-1","resources":[],"detail":{"text":"Hello, world!"}}', |
| 113 | + Timestamp: "2023-08-03T22:49:03.123Z", |
| 114 | + SignatureVersion: "1", |
| 115 | + Signature: "EXAMPLE", |
| 116 | + SigningCertUrl: "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-123456789012.pem", |
| 117 | + Subject: undefined, |
| 118 | + UnsubscribeUrl: "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456123456:my-topic:12345678-1234-1234-1234-123456789012", |
| 119 | + MessageAttributes: {} |
| 120 | + } |
| 121 | + } |
| 122 | + ] |
| 123 | + }; |
| 124 | + |
| 125 | + const extractor = new EventBridgeSNSEventTraceExtractor(tracerWrapper); |
| 126 | + |
| 127 | + const traceContext = extractor.extract(payload as SNSEvent); |
| 128 | + expect(traceContext).toBeNull(); |
| 129 | + }); |
| 130 | + |
| 131 | + it("extracts trace context from Step Function EventBridge-SNS event", () => { |
| 132 | + // Reset StepFunctionContextService instance |
| 133 | + StepFunctionContextService["_instance"] = undefined as any; |
| 134 | + |
| 135 | + const tracerWrapper = new TracerWrapper(); |
| 136 | + |
| 137 | + const payload = { |
| 138 | + "Records": [ |
| 139 | + { |
| 140 | + "EventSource": "aws:sns", |
| 141 | + "EventVersion": "1.0", |
| 142 | + "EventSubscriptionArn": "arn:aws:sns:sa-east-1:123456123456:rstrat-sfn-evb-sns-demo-dev-process-event-topic:8257bfde-3426-4901-9ace-6fbb180875b1", |
| 143 | + "Sns": { |
| 144 | + "Type": "Notification", |
| 145 | + "MessageId": "5d4b7d39-8b8d-5427-b7d9-1dc9e3d270aa", |
| 146 | + "TopicArn": "arn:aws:sns:sa-east-1:123456123456:rstrat-sfn-evb-sns-demo-dev-process-event-topic", |
| 147 | + "Message": "{\"version\":\"0\",\"id\":\"c967411c-9066-225e-1d37-527fed26f847\",\"detail-type\":\"ProcessEvent\",\"source\":\"demo.stepfunction\",\"account\":\"123456123456\",\"time\":\"2025-07-15T14:30:55Z\",\"region\":\"sa-east-1\",\"resources\":[\"arn:aws:states:sa-east-1:123456123456:stateMachine:rstrat-sfn-evb-sns-demo-dev-state-machine\",\"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-evb-sns-demo-dev-state-machine:test-execution-1752589852\"],\"detail\":{\"message\":\"Event from Step Functions\",\"timestamp\":\"2025-07-15T14:30:55.311Z\",\"executionName\":\"test-execution-1752589852\",\"stateMachineName\":\"rstrat-sfn-evb-sns-demo-dev-state-machine\",\"input\":{\"testData\":\"Hello from SNS integration\"},\"_datadog\":{\"Execution\":{\"Id\":\"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-evb-sns-demo-dev-state-machine:test-execution-1752589852\",\"StartTime\":\"2025-07-15T14:30:55.271Z\",\"Name\":\"test-execution-1752589852\",\"RoleArn\":\"arn:aws:iam::123456123456:role/rstrat-sfn-evb-sns-demo-d-StepFunctionsExecutionRol-5uB2zncEHF8x\",\"RedriveCount\":0},\"StateMachine\":{\"Id\":\"arn:aws:states:sa-east-1:123456123456:stateMachine:rstrat-sfn-evb-sns-demo-dev-state-machine\",\"Name\":\"rstrat-sfn-evb-sns-demo-dev-state-machine\"},\"State\":{\"Name\":\"PublishToEventBridge\",\"EnteredTime\":\"2025-07-15T14:30:55.311Z\",\"RetryCount\":0},\"RootExecutionId\":\"arn:aws:states:sa-east-1:123456123456:execution:rstrat-sfn-evb-sns-demo-dev-state-machine:test-execution-1752589852\",\"serverless-version\":\"v1\"}}}", |
| 148 | + "Timestamp": "2025-07-15T14:30:55.534Z", |
| 149 | + "SignatureVersion": "1", |
| 150 | + "Signature": "N+8rhLGKH/oj9wWBkvOrKHpH5icaWNxa68I0gmvxutTm+vBRZiT18GnncRR8yYPdrA6nNndb7PoNhzFdofNw3PhWx8GvaYNXQ41W4qBvM5fqg7XwrnS/+YBmmwI0Mq8uRq/+Uen/J0W8tBDxSmVkmZV8LiZceV113U/YQ8cZOorVUrbKQrCEG9c7+dydJLrFfJqT4sQ+yAxePC1ilme/OBpa2c8T9amnvuXvKRBDk2/uigvTLC9POC45p42q5jbweNzr3igXRHL20WDlR5fdqoKpZiELNeiUZKbA+caK73smQhtMUwY1vNmY8QghSSVGdOK6MgmalRsDAMF6GZRLEQ==", |
| 151 | + "SigningCertUrl": "https://sns.sa-east-1.amazonaws.com/SimpleNotificationService-9c6465fa7f48f5cacd23014631ec1136.pem", |
| 152 | + "Subject": undefined, |
| 153 | + "UnsubscribeUrl": "https://sns.sa-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:sa-east-1:123456123456:rstrat-sfn-evb-sns-demo-dev-process-event-topic:8257bfde-3426-4901-9ace-6fbb180875b1", |
| 154 | + "MessageAttributes": {} |
| 155 | + } |
| 156 | + } |
| 157 | + ] |
| 158 | + }; |
| 159 | + |
| 160 | + const extractor = new EventBridgeSNSEventTraceExtractor(tracerWrapper); |
| 161 | + |
| 162 | + const traceContext = extractor.extract(payload); |
| 163 | + expect(traceContext).not.toBeNull(); |
| 164 | + |
| 165 | + // The trace IDs are deterministically generated from the Step Function execution context |
| 166 | + expect(traceContext?.toTraceId()).toBe("2458939194637197377"); |
| 167 | + expect(traceContext?.toSpanId()).toBe("6978708559187765983"); |
| 168 | + expect(traceContext?.sampleMode()).toBe("1"); |
| 169 | + expect(traceContext?.source).toBe("event"); |
| 170 | + }); |
| 171 | + }); |
| 172 | +}); |
0 commit comments