Skip to content

Commit 74e990c

Browse files
test: add comprehensive tests for image generation tracing
Signed-off-by: Hrushikesh Patil <[email protected]>
1 parent ce13634 commit 74e990c

File tree

2 files changed

+592
-0
lines changed

2 files changed

+592
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright Envoy AI Gateway Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
// The full text of the Apache license is available in the LICENSE file at
4+
// the root of the repo.
5+
6+
package tracing
7+
8+
import (
9+
"encoding/json"
10+
"testing"
11+
12+
"github.com/stretchr/testify/require"
13+
"go.opentelemetry.io/otel/attribute"
14+
oteltrace "go.opentelemetry.io/otel/trace"
15+
16+
"github.com/envoyproxy/ai-gateway/internal/testing/testotel"
17+
openaisdk "github.com/openai/openai-go/v2"
18+
)
19+
20+
// Test data for image generation span tests
21+
var (
22+
basicImageResp = &openaisdk.ImagesResponse{
23+
Data: []openaisdk.Image{
24+
{URL: "https://example.com/img1.png"},
25+
{URL: "https://example.com/img2.png"},
26+
},
27+
Size: openaisdk.ImagesResponseSize1024x1024,
28+
Usage: openaisdk.ImagesResponseUsage{
29+
InputTokens: 8,
30+
OutputTokens: 1056,
31+
TotalTokens: 1064,
32+
},
33+
}
34+
)
35+
36+
// Mock recorder for testing image generation span
37+
type testImageGenerationRecorder struct{}
38+
39+
func (r testImageGenerationRecorder) StartParams(req *openaisdk.ImageGenerateParams, body []byte) (string, []oteltrace.SpanStartOption) {
40+
return "ImageGeneration", nil
41+
}
42+
43+
func (r testImageGenerationRecorder) RecordRequest(span oteltrace.Span, req *openaisdk.ImageGenerateParams, body []byte) {
44+
span.SetAttributes(
45+
attribute.String("model", string(req.Model)),
46+
attribute.String("prompt", req.Prompt),
47+
attribute.String("size", string(req.Size)),
48+
)
49+
}
50+
51+
func (r testImageGenerationRecorder) RecordResponse(span oteltrace.Span, resp *openaisdk.ImagesResponse) {
52+
respBytes, _ := json.Marshal(resp)
53+
span.SetAttributes(
54+
attribute.Int("statusCode", 200),
55+
attribute.Int("respBodyLen", len(respBytes)),
56+
attribute.Int("imageCount", len(resp.Data)),
57+
)
58+
}
59+
60+
func (r testImageGenerationRecorder) RecordResponseOnError(span oteltrace.Span, statusCode int, body []byte) {
61+
span.SetAttributes(
62+
attribute.Int("statusCode", statusCode),
63+
attribute.String("errorBody", string(body)),
64+
)
65+
}
66+
67+
func TestImageGenerationSpan_RecordResponse(t *testing.T) {
68+
resp := &openaisdk.ImagesResponse{
69+
Data: []openaisdk.Image{{URL: "https://example.com/test.png"}},
70+
Size: openaisdk.ImagesResponseSize1024x1024,
71+
Usage: openaisdk.ImagesResponseUsage{
72+
InputTokens: 5,
73+
OutputTokens: 100,
74+
TotalTokens: 105,
75+
},
76+
}
77+
respBytes, err := json.Marshal(resp)
78+
require.NoError(t, err)
79+
80+
s := &imageGenerationSpan{recorder: testImageGenerationRecorder{}}
81+
actualSpan := testotel.RecordWithSpan(t, func(span oteltrace.Span) bool {
82+
s.span = span
83+
s.RecordResponse(resp)
84+
return false // Recording response shouldn't end the span.
85+
})
86+
87+
require.Equal(t, []attribute.KeyValue{
88+
attribute.Int("statusCode", 200),
89+
attribute.Int("respBodyLen", len(respBytes)),
90+
attribute.Int("imageCount", 1),
91+
}, actualSpan.Attributes)
92+
}
93+
94+
func TestImageGenerationSpan_EndSpan(t *testing.T) {
95+
s := &imageGenerationSpan{recorder: testImageGenerationRecorder{}}
96+
actualSpan := testotel.RecordWithSpan(t, func(span oteltrace.Span) bool {
97+
s.span = span
98+
s.EndSpan()
99+
return true // EndSpan ends the underlying span.
100+
})
101+
102+
// EndSpan should not add any attributes, just end the span
103+
require.Empty(t, actualSpan.Attributes)
104+
}
105+
106+
func TestImageGenerationSpan_EndSpanOnError(t *testing.T) {
107+
errorMsg := "image generation failed"
108+
actualSpan := testotel.RecordWithSpan(t, func(span oteltrace.Span) bool {
109+
s := &imageGenerationSpan{span: span, recorder: testImageGenerationRecorder{}}
110+
s.EndSpanOnError(500, []byte(errorMsg))
111+
return true // EndSpanOnError ends the underlying span.
112+
})
113+
114+
require.Equal(t, []attribute.KeyValue{
115+
attribute.Int("statusCode", 500),
116+
attribute.String("errorBody", errorMsg),
117+
}, actualSpan.Attributes)
118+
}
119+
120+
func TestImageGenerationSpan_RecordResponse_WithMultipleImages(t *testing.T) {
121+
resp := &openaisdk.ImagesResponse{
122+
Data: []openaisdk.Image{
123+
{URL: "https://example.com/img1.png"},
124+
{URL: "https://example.com/img2.png"},
125+
{URL: "https://example.com/img3.png"},
126+
},
127+
Size: openaisdk.ImagesResponseSize1024x1024,
128+
Usage: openaisdk.ImagesResponseUsage{
129+
InputTokens: 10,
130+
OutputTokens: 200,
131+
TotalTokens: 210,
132+
},
133+
}
134+
respBytes, err := json.Marshal(resp)
135+
require.NoError(t, err)
136+
137+
s := &imageGenerationSpan{recorder: testImageGenerationRecorder{}}
138+
actualSpan := testotel.RecordWithSpan(t, func(span oteltrace.Span) bool {
139+
s.span = span
140+
s.RecordResponse(resp)
141+
return false
142+
})
143+
144+
require.Equal(t, []attribute.KeyValue{
145+
attribute.Int("statusCode", 200),
146+
attribute.Int("respBodyLen", len(respBytes)),
147+
attribute.Int("imageCount", 3),
148+
}, actualSpan.Attributes)
149+
}
150+
151+
func TestImageGenerationSpan_EndSpanOnError_WithDifferentStatusCodes(t *testing.T) {
152+
tests := []struct {
153+
name string
154+
statusCode int
155+
errorBody string
156+
}{
157+
{
158+
name: "bad request",
159+
statusCode: 400,
160+
errorBody: `{"error":{"message":"Invalid prompt","type":"invalid_request_error"}}`,
161+
},
162+
{
163+
name: "rate limit",
164+
statusCode: 429,
165+
errorBody: `{"error":{"message":"Rate limit exceeded","type":"rate_limit_error"}}`,
166+
},
167+
{
168+
name: "server error",
169+
statusCode: 500,
170+
errorBody: `{"error":{"message":"Internal server error","type":"server_error"}}`,
171+
},
172+
}
173+
174+
for _, tt := range tests {
175+
t.Run(tt.name, func(t *testing.T) {
176+
actualSpan := testotel.RecordWithSpan(t, func(span oteltrace.Span) bool {
177+
s := &imageGenerationSpan{span: span, recorder: testImageGenerationRecorder{}}
178+
s.EndSpanOnError(tt.statusCode, []byte(tt.errorBody))
179+
return true
180+
})
181+
182+
require.Equal(t, []attribute.KeyValue{
183+
attribute.Int("statusCode", tt.statusCode),
184+
attribute.String("errorBody", tt.errorBody),
185+
}, actualSpan.Attributes)
186+
})
187+
}
188+
}

0 commit comments

Comments
 (0)