Skip to content

Commit 93feb9a

Browse files
authored
tests(snapshot): deserialize request on snapshot (#119)
1 parent 7cb27f2 commit 93feb9a

File tree

11 files changed

+945
-3089
lines changed

11 files changed

+945
-3089
lines changed

gateway/test/otel.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
interface OtlpAttribute {
2+
key: string
3+
value: OtlpValue
4+
}
5+
6+
interface OtlpValue {
7+
stringValue?: string
8+
intValue?: number
9+
doubleValue?: number
10+
boolValue?: boolean
11+
arrayValue?: { values?: OtlpValue[] }
12+
kvlistValue?: { values?: { key: string; value: OtlpValue }[] }
13+
}
14+
15+
/**
16+
* Deserializes OTLP trace data from a JSON string and transforms it into a cleaner JSON structure.
17+
* This extracts the key information from the OTLP format (resource spans, scope spans, etc.)
18+
* and returns a more readable representation suitable for test snapshots.
19+
*/
20+
export function deserializeRequest(data: string) {
21+
const otlpData = JSON.parse(data)
22+
23+
// Transform OTLP format into cleaner JSON
24+
const spans = []
25+
26+
for (const resourceSpan of otlpData.resourceSpans || []) {
27+
const resource = resourceSpan.resource?.attributes || []
28+
29+
for (const scopeSpan of resourceSpan.scopeSpans || []) {
30+
const scope = scopeSpan.scope?.name
31+
32+
for (const span of scopeSpan.spans || []) {
33+
// Convert attributes array to object
34+
const attributes: Record<string, unknown> = {}
35+
for (const attr of span.attributes || []) {
36+
attributes[attr.key] = extractOtlpValue(attr.value)
37+
}
38+
39+
spans.push({
40+
name: span.name,
41+
parentSpanId: span.parentSpanId,
42+
kind: span.kind,
43+
attributes,
44+
status: span.status,
45+
events: span.events,
46+
links: span.links,
47+
resource: Object.fromEntries(
48+
resource.map((attr: OtlpAttribute) => [
49+
attr.key,
50+
attr.value.stringValue ??
51+
attr.value.intValue ??
52+
attr.value.doubleValue ??
53+
attr.value.boolValue ??
54+
attr.value,
55+
]),
56+
),
57+
scope,
58+
})
59+
}
60+
}
61+
}
62+
63+
return spans
64+
}
65+
66+
/**
67+
* Recursively extracts the actual value from an OTLP AnyValue structure.
68+
* Handles all OTLP value types including nested structures.
69+
*/
70+
function extractOtlpValue(value: OtlpValue): unknown {
71+
if (value.stringValue !== undefined) return value.stringValue
72+
if (value.intValue !== undefined) return value.intValue
73+
if (value.doubleValue !== undefined) return value.doubleValue
74+
if (value.boolValue !== undefined) return value.boolValue
75+
76+
if (value.arrayValue) {
77+
// For arrays, just extract the values directly without wrapping
78+
return value.arrayValue.values?.map(extractOtlpValue) || []
79+
}
80+
81+
if (value.kvlistValue) {
82+
// For key-value lists, extract as an object
83+
const obj: Record<string, unknown> = {}
84+
for (const kv of value.kvlistValue.values || []) {
85+
obj[kv.key] = extractOtlpValue(kv.value)
86+
}
87+
return obj
88+
}
89+
90+
// For empty objects or unknown types, return the raw value
91+
return value
92+
}

gateway/test/providers/anthropic.spec.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Anthropic from '@anthropic-ai/sdk'
22
import { describe, expect } from 'vitest'
3+
import { deserializeRequest } from '../otel'
34
import { test } from '../setup'
45

56
describe('anthropic', () => {
@@ -21,7 +22,7 @@ describe('anthropic', () => {
2122
})
2223
expect(completion).toMatchSnapshot('llm')
2324
expect(otelBatch, 'otelBatch length not 1').toHaveLength(1)
24-
expect(JSON.parse(otelBatch[0]!).resourceSpans?.[0].scopeSpans?.[0].spans?.[0]?.attributes).toMatchSnapshot('span')
25+
expect(deserializeRequest(otelBatch[0]!)).toMatchSnapshot('span')
2526
})
2627

2728
test('should call anthropic via gateway with builtin tools', async ({ gateway }) => {
@@ -40,7 +41,7 @@ describe('anthropic', () => {
4041
})
4142
expect(response).toMatchSnapshot('llm')
4243
expect(otelBatch, 'otelBatch length not 1').toHaveLength(1)
43-
expect(JSON.parse(otelBatch[0]!).resourceSpans?.[0].scopeSpans?.[0].spans?.[0]?.attributes).toMatchSnapshot('span')
44+
expect(deserializeRequest(otelBatch[0]!)).toMatchSnapshot('span')
4445
})
4546

4647
test('should call anthropic via gateway with stream', async ({ gateway }) => {
@@ -61,6 +62,6 @@ describe('anthropic', () => {
6162

6263
expect(chunks).toMatchSnapshot('chunks')
6364
expect(otelBatch, 'otelBatch length not 1').toHaveLength(1)
64-
expect(JSON.parse(otelBatch[0]!).resourceSpans?.[0].scopeSpans?.[0].spans?.[0]?.attributes).toMatchSnapshot('span')
65+
expect(deserializeRequest(otelBatch[0]!)).toMatchSnapshot('span')
6566
})
6667
})

0 commit comments

Comments
 (0)