Files
grafana/pkg/tsdb/zipkin/client_test.go
Ivana Huckova 5e23b2c07f Zipkin: Improve error source (#99099)
* Zipkin: Improve error source

* Revert file name change
2025-01-17 10:04:33 +01:00

389 lines
10 KiB
Go

package zipkin
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/openzipkin/zipkin-go/model"
"github.com/stretchr/testify/assert"
)
func TestZipkinClient_Services(t *testing.T) {
tests := []struct {
name string
mockResponse string
mockStatusCode int
mockStatus string
expectedResult []string
expectError bool
expectedError error
}{
{
name: "Successful response",
mockResponse: `["service1", "service2"]`,
mockStatusCode: http.StatusOK,
mockStatus: "OK",
expectedResult: []string{"service1", "service2"},
expectError: false,
expectedError: nil,
},
{
name: "Non-200 response",
mockResponse: "",
mockStatusCode: http.StatusInternalServerError,
mockStatus: "Internal Server Error",
expectedResult: []string{},
expectError: true,
expectedError: backend.DownstreamError(fmt.Errorf("request failed: Internal Server Error")),
},
{
name: "Invalid JSON response",
mockResponse: `{invalid json`,
mockStatusCode: http.StatusOK,
mockStatus: "OK",
expectedResult: []string{},
expectError: true,
expectedError: errors.New("invalid character 'i' looking for beginning of value"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/v2/services", r.URL.Path)
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write([]byte(tt.mockResponse))
}))
defer server.Close()
client, _ := New(server.URL, server.Client(), log.New())
services, err := client.Services()
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expectedResult, services)
})
}
}
func TestZipkinClient_Spans(t *testing.T) {
tests := []struct {
name string
serviceName string
mockResponse string
mockStatusCode int
expectedResult []string
expectError bool
}{
{
name: "Successful response",
serviceName: "service1",
mockResponse: `["span1", "span2"]`,
mockStatusCode: http.StatusOK,
expectedResult: []string{"span1", "span2"},
expectError: false,
},
{
name: "Non-200 response",
serviceName: "service1",
mockResponse: "",
mockStatusCode: http.StatusNotFound,
expectedResult: []string{},
expectError: true,
},
{
name: "Invalid JSON response",
serviceName: "service1",
mockResponse: `{invalid json`,
mockStatusCode: http.StatusOK,
expectedResult: []string{},
expectError: true,
},
{
name: "Empty serviceName",
serviceName: "",
mockResponse: "",
mockStatusCode: http.StatusOK,
expectedResult: []string{},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/v2/spans", r.URL.Path)
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write([]byte(tt.mockResponse))
}))
defer server.Close()
client, _ := New(server.URL, server.Client(), log.New())
spans, err := client.Spans(tt.serviceName)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expectedResult, spans)
})
}
}
func TestZipkinClient_Traces(t *testing.T) {
tests := []struct {
name string
serviceName string
spanName string
mockResponse interface{}
mockStatusCode int
expectedResult [][]model.SpanModel
expectError bool
expectedError string
}{
{
name: "Successful response",
serviceName: "service1",
spanName: "span1",
mockResponse: [][]model.SpanModel{{{SpanContext: model.SpanContext{TraceID: model.TraceID{Low: 1234}, ID: 1}, Name: "operation1", Tags: map[string]string{"key1": "value1"}}}},
mockStatusCode: http.StatusOK,
expectedResult: [][]model.SpanModel{{{SpanContext: model.SpanContext{TraceID: model.TraceID{Low: 1234}, ID: 1}, Name: "operation1", Tags: map[string]string{"key1": "value1"}}}},
expectError: false,
expectedError: "",
},
{
name: "Non-200 response",
serviceName: "service1",
spanName: "span1",
mockResponse: nil,
mockStatusCode: http.StatusForbidden,
expectedResult: [][]model.SpanModel{},
expectError: true,
expectedError: "EOF",
},
{
name: "Empty serviceName",
serviceName: "",
spanName: "span1",
mockResponse: nil,
mockStatusCode: http.StatusOK,
expectedResult: [][]model.SpanModel{},
expectError: true,
expectedError: "invalid/empty serviceName",
},
{
name: "Empty spanName",
serviceName: "service1",
spanName: "",
mockResponse: nil,
mockStatusCode: http.StatusOK,
expectedResult: [][]model.SpanModel{},
expectError: true,
expectedError: "invalid/empty spanName",
},
{
name: "Valid response with empty trace list",
serviceName: "service1",
spanName: "span1",
mockResponse: [][]model.SpanModel{},
mockStatusCode: http.StatusOK,
expectedResult: [][]model.SpanModel{},
expectError: false,
expectedError: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var response []byte
if mockData, ok := tt.mockResponse.([][]model.SpanModel); ok {
response, _ = json.Marshal(mockData)
} else if str, ok := tt.mockResponse.(string); ok {
response = []byte(str)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/v2/traces", r.URL.Path)
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write(response)
}))
defer server.Close()
client, _ := New(server.URL, server.Client(), log.New())
traces, err := client.Traces(tt.serviceName, tt.spanName)
if tt.expectError {
assert.Error(t, err)
assert.Equal(t, err.Error(), tt.expectedError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expectedResult, traces)
})
}
}
func TestZipkinClient_Trace(t *testing.T) {
tests := []struct {
name string
traceID string
mockResponse string
mockStatusCode int
expectedResult []model.SpanModel
expectError bool
expectedError string
}{
{
name: "Successful response",
traceID: "trace-id",
mockResponse: `[{"traceId":"00000000000004d2","id":"0000000000000001","name":"operation1","tags":{"key1":"value1"}}]`,
mockStatusCode: http.StatusOK,
expectedResult: []model.SpanModel{
{
SpanContext: model.SpanContext{
TraceID: model.TraceID{Low: 1234},
ID: model.ID(1),
},
Name: "operation1",
Tags: map[string]string{"key1": "value1"},
},
},
expectError: false,
expectedError: "",
},
{
name: "Invalid traceID",
traceID: "",
mockResponse: "",
mockStatusCode: http.StatusOK,
expectedResult: []model.SpanModel{},
expectError: true,
expectedError: "invalid/empty traceId",
},
{
name: "Special characters traceID",
traceID: "a/b",
mockResponse: `[{"traceId":"00000000000004d2","id":"0000000000000001","name":"operation1","tags":{"key1":"value1"}}]`,
mockStatusCode: http.StatusOK,
expectedResult: []model.SpanModel{
{
SpanContext: model.SpanContext{
TraceID: model.TraceID{Low: 1234},
ID: model.ID(1),
},
Name: "operation1",
Tags: map[string]string{"key1": "value1"},
},
},
expectError: false,
expectedError: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var client ZipkinClient
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Contains(t, r.URL.String(), "/api/v2/trace/"+url.QueryEscape(tt.traceID))
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write([]byte(tt.mockResponse))
}))
defer server.Close()
client, _ = New(server.URL, server.Client(), log.New())
trace, err := client.Trace(tt.traceID)
if tt.expectError {
assert.Error(t, err)
assert.Empty(t, trace)
assert.Equal(t, tt.expectedError, err.Error())
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, trace)
}
})
}
}
func TestCreateZipkinURL(t *testing.T) {
tests := []struct {
name string
baseURL string
path string
params map[string]string
expected string
shouldErr bool
}{
{
name: "WithPathAndParams",
baseURL: "http://example.com",
path: "api/v1/trace",
params: map[string]string{"key1": "value1", "key2": "value2"},
expected: "http://example.com/api/v1/trace?key1=value1&key2=value2",
},
{
name: "OnlyParams",
baseURL: "http://example.com",
path: "",
params: map[string]string{"key1": "value1"},
expected: "http://example.com?key1=value1",
},
{
name: "NoParams",
baseURL: "http://example.com",
path: "api/v1/trace",
params: map[string]string{},
expected: "http://example.com/api/v1/trace",
},
{
name: "InvalidBaseURL",
baseURL: "http://example .com",
path: "api/v1/trace",
params: map[string]string{},
shouldErr: true,
},
{
name: "BaseURLWithPath",
baseURL: "http://example.com/base",
path: "api/v1/trace",
params: map[string]string{"key1": "value1"},
expected: "http://example.com/base/api/v1/trace?key1=value1",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result, err := createZipkinURL(tc.baseURL, tc.path, tc.params)
if tc.shouldErr {
if err == nil {
t.Fatalf("Expected error, but got nil")
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if result != tc.expected {
t.Errorf("Expected %s, got %s", tc.expected, result)
}
})
}
}