mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 04:52:10 +08:00

* jaeger backend migration * move processing to JaegerClient.Search * fix upload query error source type * suggestions * lint * fix link to traceid query * fix tests * fix tests * use consistent types * add tests for TransformSearchResponse fn * test search function * fix filtering using tags * suggestion * remove unnecessary arguments * use logfmt parser for tags * test * test * use logfmt for query tags * update * go fmt * run backend for all queryTypes * run make update-workspace * assign owner to logfmt pkg in go.mod * apply suggestions * update tests * trigger workflows?
491 lines
14 KiB
Go
491 lines
14 KiB
Go
package jaeger
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestJaegerClient_Services(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockResponse string
|
|
mockStatusCode int
|
|
mockStatus string
|
|
expectedResult []string
|
|
expectError bool
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Successful response",
|
|
mockResponse: `{"data": ["service1", "service2"], "total": 2, "limit": 0, "offset": 0}`,
|
|
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: errors.New("Internal Server Error"),
|
|
},
|
|
{
|
|
name: "Invalid JSON response",
|
|
mockResponse: `{invalid json`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedResult: []string{},
|
|
expectError: true,
|
|
expectedError: &json.SyntaxError{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(tt.mockStatusCode)
|
|
_, _ = w.Write([]byte(tt.mockResponse))
|
|
}))
|
|
defer server.Close()
|
|
|
|
settings := backend.DataSourceInstanceSettings{
|
|
URL: server.URL,
|
|
}
|
|
client, err := New(server.Client(), log.NewNullLogger(), settings)
|
|
assert.NoError(t, err)
|
|
|
|
services, err := client.Services()
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
if tt.expectedError != nil {
|
|
assert.IsType(t, tt.expectedError, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.expectedResult, services)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJaegerClient_Operations(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
service string
|
|
mockResponse string
|
|
mockStatusCode int
|
|
mockStatus string
|
|
expectedResult []string
|
|
expectError bool
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Successful response",
|
|
service: "test-service",
|
|
mockResponse: `{"data": ["operation1", "operation2"], "total": 2, "limit": 0, "offset": 0}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedResult: []string{"operation1", "operation2"},
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Non-200 response",
|
|
service: "test-service",
|
|
mockResponse: "",
|
|
mockStatusCode: http.StatusInternalServerError,
|
|
mockStatus: "Internal Server Error",
|
|
expectedResult: []string{},
|
|
expectError: true,
|
|
expectedError: errors.New("Internal Server Error"),
|
|
},
|
|
{
|
|
name: "Invalid JSON response",
|
|
service: "test-service",
|
|
mockResponse: `{invalid json`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedResult: []string{},
|
|
expectError: true,
|
|
expectedError: &json.SyntaxError{},
|
|
},
|
|
{
|
|
name: "Service with special characters",
|
|
service: "test/service:1",
|
|
mockResponse: `{"data": ["operation1"], "total": 1, "limit": 0, "offset": 0}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedResult: []string{"operation1"},
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Empty service",
|
|
service: "",
|
|
mockResponse: `{"data": [], "total": 0, "limit": 0, "offset": 0}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedResult: []string{},
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(tt.mockStatusCode)
|
|
_, _ = w.Write([]byte(tt.mockResponse))
|
|
}))
|
|
defer server.Close()
|
|
|
|
settings := backend.DataSourceInstanceSettings{
|
|
URL: server.URL,
|
|
}
|
|
client, err := New(server.Client(), log.NewNullLogger(), settings)
|
|
assert.NoError(t, err)
|
|
|
|
operations, err := client.Operations(tt.service)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
if tt.expectedError != nil {
|
|
assert.IsType(t, tt.expectedError, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.expectedResult, operations)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJaegerClient_Search(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
query *JaegerQuery
|
|
start int64
|
|
end int64
|
|
mockResponse string
|
|
mockStatusCode int
|
|
expectedURL string
|
|
expectError bool
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Successful search with all parameters",
|
|
query: &JaegerQuery{
|
|
Service: "test-service",
|
|
Operation: "test-operation",
|
|
Tags: "error=true",
|
|
MinDuration: "1s",
|
|
MaxDuration: "5s",
|
|
Limit: 10,
|
|
},
|
|
start: 1735689600000000,
|
|
end: 1738368000000000,
|
|
mockResponse: `{"data":[{"traceID":"test-trace-id"}]}`,
|
|
mockStatusCode: http.StatusOK,
|
|
expectedURL: "/api/traces?end=1738368000000000&limit=10&maxDuration=5s&minDuration=1s&operation=test-operation&service=test-service&start=1735689600000000&tags=%7B%22error%22%3A%22true%22%7D",
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Successful search with minimal parameters",
|
|
query: &JaegerQuery{
|
|
Service: "test-service",
|
|
},
|
|
start: 1735689600000000,
|
|
end: 1738368000000000,
|
|
mockResponse: `{"data":[{"traceID":"test-trace-id"}]}`,
|
|
mockStatusCode: http.StatusOK,
|
|
expectedURL: "/api/traces?end=1738368000000000&service=test-service&start=1735689600000000",
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Server error",
|
|
query: &JaegerQuery{
|
|
Service: "test-service",
|
|
},
|
|
start: 1735689600000000,
|
|
end: 1738368000000000,
|
|
mockResponse: "",
|
|
mockStatusCode: http.StatusInternalServerError,
|
|
expectedURL: "/api/traces?end=1738368000000000&service=test-service&start=1735689600000000",
|
|
expectError: true,
|
|
expectedError: backend.DownstreamError(fmt.Errorf("request failed: %s", http.StatusText(http.StatusInternalServerError))),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var actualURL string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
actualURL = r.URL.String()
|
|
w.WriteHeader(tt.mockStatusCode)
|
|
_, _ = w.Write([]byte(tt.mockResponse))
|
|
}))
|
|
defer server.Close()
|
|
|
|
settings := backend.DataSourceInstanceSettings{
|
|
URL: server.URL,
|
|
}
|
|
client, err := New(server.Client(), log.NewNullLogger(), settings)
|
|
assert.NoError(t, err)
|
|
traces, err := client.Search(tt.query, tt.start, tt.end)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
if tt.expectedError != nil {
|
|
assert.IsType(t, tt.expectedError, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, traces)
|
|
assert.Equal(t, tt.expectedURL, actualURL)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJaegerClient_Trace(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
traceId string
|
|
jsonData string
|
|
start int64
|
|
end int64
|
|
mockResponse string
|
|
mockStatusCode int
|
|
mockStatus string
|
|
expectedURL string
|
|
expectError bool
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Successful response with time params enabled",
|
|
traceId: "abc123",
|
|
jsonData: `{"traceIdTimeParams": {"enabled": true}}`,
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: `{"data":[{"traceID":"abc123"}]}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedURL: "/api/traces/abc123?end=2000&start=1000",
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Successful response with time params disabled",
|
|
traceId: "abc123",
|
|
jsonData: `{"traceIdTimeParams": {"enabled": false}}`,
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: `{"data":[{"traceID":"abc123"}]}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedURL: "/api/traces/abc123",
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Non-200 response",
|
|
traceId: "abc123",
|
|
jsonData: `{"traceIdTimeParams": {"enabled": true}}`,
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: "",
|
|
mockStatusCode: http.StatusInternalServerError,
|
|
mockStatus: "Internal Server Error",
|
|
expectedURL: "/api/traces/abc123?end=2000&start=1000",
|
|
expectError: true,
|
|
expectedError: backend.PluginError(errors.New("Internal Server Error")),
|
|
},
|
|
{
|
|
name: "Invalid JSON response",
|
|
traceId: "abc123",
|
|
jsonData: `{"traceIdTimeParams": {"enabled": true}}`,
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: `{invalid json`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedURL: "/api/traces/abc123?end=2000&start=1000",
|
|
expectError: true,
|
|
expectedError: &json.SyntaxError{},
|
|
},
|
|
{
|
|
name: "Empty trace ID",
|
|
traceId: "",
|
|
jsonData: `{"traceIdTimeParams": {"enabled": true}}`,
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: `{"data":[]}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedURL: "",
|
|
expectError: true,
|
|
expectedError: backend.DownstreamError(errors.New("traceID is empty")),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var actualURL string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
actualURL = r.URL.String()
|
|
w.WriteHeader(tt.mockStatusCode)
|
|
_, _ = w.Write([]byte(tt.mockResponse))
|
|
}))
|
|
defer server.Close()
|
|
|
|
settings := backend.DataSourceInstanceSettings{
|
|
URL: server.URL,
|
|
JSONData: []byte(tt.jsonData),
|
|
}
|
|
client, err := New(server.Client(), log.NewNullLogger(), settings)
|
|
assert.NoError(t, err)
|
|
|
|
trace, err := client.Trace(context.Background(), tt.traceId, tt.start, tt.end)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
if tt.expectedError != nil {
|
|
assert.IsType(t, tt.expectedError, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, trace)
|
|
}
|
|
assert.Equal(t, tt.expectedURL, actualURL)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJaegerClient_Dependencies(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
start int64
|
|
end int64
|
|
mockResponse string
|
|
mockStatusCode int
|
|
mockStatus string
|
|
expectedURL string
|
|
expectError bool
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Successful response with time range",
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: `{"data":[{"parent":"serviceA","child":"serviceB","callCount":1}]}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedURL: "/api/dependencies?endTs=2000&lookback=1000",
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Successful response without time range",
|
|
start: 0,
|
|
end: 0,
|
|
mockResponse: `{"data":[{"parent":"serviceA","child":"serviceB","callCount":1}]}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedURL: "/api/dependencies",
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Non-200 response",
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: "",
|
|
mockStatusCode: http.StatusInternalServerError,
|
|
mockStatus: "Internal Server Error",
|
|
expectedURL: "/api/dependencies?endTs=2000&lookback=1000",
|
|
expectError: true,
|
|
expectedError: backend.PluginError(errors.New("Internal Server Error")),
|
|
},
|
|
{
|
|
name: "Invalid JSON response",
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: `{invalid json`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedURL: "/api/dependencies?endTs=2000&lookback=1000",
|
|
expectError: true,
|
|
expectedError: &json.SyntaxError{},
|
|
},
|
|
{
|
|
name: "Empty dependencies response and no errors",
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: `{"data":[]}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedURL: "/api/dependencies?endTs=2000&lookback=1000",
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Response with errors",
|
|
start: 1000,
|
|
end: 2000,
|
|
mockResponse: `{"data":[],"errors":[{"code":500,"msg":"Internal error"}]}`,
|
|
mockStatusCode: http.StatusOK,
|
|
mockStatus: "OK",
|
|
expectedURL: "/api/dependencies?endTs=2000&lookback=1000",
|
|
expectError: false,
|
|
expectedError: nil,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var actualURL string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
actualURL = r.URL.String()
|
|
w.WriteHeader(tt.mockStatusCode)
|
|
_, _ = w.Write([]byte(tt.mockResponse))
|
|
}))
|
|
defer server.Close()
|
|
|
|
settings := backend.DataSourceInstanceSettings{
|
|
URL: server.URL,
|
|
}
|
|
client, err := New(server.Client(), log.NewNullLogger(), settings)
|
|
assert.NoError(t, err)
|
|
|
|
dependencies, err := client.Dependencies(context.Background(), tt.start, tt.end)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
if tt.expectedError != nil {
|
|
assert.IsType(t, tt.expectedError, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, dependencies)
|
|
}
|
|
assert.Equal(t, tt.expectedURL, actualURL)
|
|
})
|
|
}
|
|
}
|