Files
grafana/pkg/tsdb/graphite/graphite_test.go
Andreas Christou df2bb6be0a Graphite: Backend tag values autocomplete endpoint (#110773)
* Add lint rules

* Backend decoupling

- Add standalone files
- Add graphite query type
- Add logger to Service
- Create logger in the ProvideService method
- Use a pointer for the HTTP client provider
- Update logger usage everywhere
- Update tracer type
- Replace simplejson with json
- Add dummy CallResource and CheckHealth methods
- Update tests

* Update ConfigEditor imports

* Update types imports

* Update datasource

- Switch to using semver package
- Update imports

* Update store imports

* Update helper imports and notification creation

* Update context import

* Update version numbers and logic

* Copy array_move from core

* Test updates

* Add required files and update plugin.json

* Update core references and packages

* Remove commented code

* Update wire

* Lint

* Fix import

* Copy null type

* More lint

* Update snapshot

* Refactor backend

- Split query logic into separate file
- Move utils to separate file

* Add health-check logic

- Support backend healthcheck if the FF is enabled

* Remove query import support as unneeded

* Add test

* Add util function for decoding responses

* Add events types

* Add resource handler

* Add events handler and generic resource req handler

* Tests

* Update frontend

- Add types
- Update events function to support backend requests

* Lint and typing

* Lint

* Add metrics find endpoint

- Add types
- Add generic response parser
- Add endpoint
- Tests

* Update FE functoin to use backend endpoint

* Lint

* Simplify request

* Update test

* Metrics expand type

* Extract shared logic and add metric expand endpoint

* Update tests

* Call metric expand from backend

* Rename type for clarity

* Add get resource req handler

* Refactor doGraphiteRequest, parseResponse

Update tests

* Migrate functions endpoint to backend

* Support tags autocomplete in backend

- Add tests
- Add types
- Remove unneeded comments

* Support tag values autocomplete

- Remove unused frontend endpoints
- Add types
- Update tests

* Add tests

* Review

* Review

* Fix packages

* Format

* Fix merge issues

* Review

* Fix undefined values

* Extract request creation

- Add method for create requests generically with tests
- Replace usage in query method
- Update usages in resource handlers
- Update tests
- Update types

* Lint
2025-09-15 11:35:29 +01:00

260 lines
6.1 KiB
Go

package graphite
import (
"context"
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_CreateRequest(t *testing.T) {
ctx := context.Background()
service := &Service{}
dsInfo := &datasourceInfo{
URL: "http://graphite.example.com",
}
tests := []struct {
name string
dsInfo *datasourceInfo
params URLParams
expectedURL string
expectedMethod string
expectedError string
checkHeaders map[string]string
checkQuery map[string][]string
}{
{
name: "basic request with default GET method",
dsInfo: dsInfo,
params: URLParams{},
expectedURL: "http://graphite.example.com",
expectedMethod: "GET",
},
{
name: "request with subpath",
dsInfo: dsInfo,
params: URLParams{
SubPath: "/metrics/find",
},
expectedURL: "http://graphite.example.com/metrics/find",
expectedMethod: "GET",
},
{
name: "request with custom method",
dsInfo: dsInfo,
params: URLParams{
Method: "POST",
},
expectedURL: "http://graphite.example.com",
expectedMethod: "POST",
},
{
name: "request with query parameters",
dsInfo: dsInfo,
params: URLParams{
QueryParams: map[string][]string{
"query": {"stats.counters.*"},
"format": {"json"},
},
},
expectedURL: "http://graphite.example.com",
expectedMethod: "GET",
checkQuery: map[string][]string{
"query": {"stats.counters.*"},
"format": {"json"},
},
},
{
name: "request with headers",
dsInfo: dsInfo,
params: URLParams{
Headers: map[string]string{
"Content-Type": "application/json",
},
},
expectedURL: "http://graphite.example.com",
expectedMethod: "GET",
checkHeaders: map[string]string{
"Content-Type": "application/json",
},
},
{
name: "request with body",
dsInfo: dsInfo,
params: URLParams{
Method: "POST",
Body: strings.NewReader(`{"test": "data"}`),
},
expectedURL: "http://graphite.example.com",
expectedMethod: "POST",
},
{
name: "complex request with all parameters",
dsInfo: dsInfo,
params: URLParams{
SubPath: "/metrics/expand",
Method: "POST",
QueryParams: map[string][]string{
"groupByExpr": {"true"},
"leavesOnly": {"false"},
},
Headers: map[string]string{
"X-Custom-Header": "test-value",
},
Body: strings.NewReader(`{"query": "stats.*"}`),
},
expectedURL: "http://graphite.example.com/metrics/expand",
expectedMethod: "POST",
checkQuery: map[string][]string{
"groupByExpr": {"true"},
"leavesOnly": {"false"},
},
checkHeaders: map[string]string{
"X-Custom-Header": "test-value",
},
},
{
name: "invalid URL in datasource",
dsInfo: &datasourceInfo{
URL: "://invalid-url",
},
params: URLParams{},
expectedError: "missing protocol scheme",
},
{
name: "empty query parameter values",
dsInfo: dsInfo,
params: URLParams{
QueryParams: map[string][]string{
"empty": {""},
"valid": {"value"},
},
},
expectedURL: "http://graphite.example.com",
expectedMethod: "GET",
checkQuery: map[string][]string{
"empty": {""},
"valid": {"value"},
},
},
{
name: "multi-valued query parameter",
dsInfo: dsInfo,
params: URLParams{
QueryParams: map[string][]string{
"valid": {"value1", "value2"},
},
},
expectedURL: "http://graphite.example.com",
expectedMethod: "GET",
checkQuery: map[string][]string{
"valid": {"value1", "value2"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, err := service.createRequest(ctx, tt.dsInfo, tt.params)
if tt.expectedError != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
return
}
require.NoError(t, err)
require.NotNil(t, req)
// Check URL (base URL without query parameters)
baseURL := req.URL.Scheme + "://" + req.URL.Host + req.URL.Path
assert.Equal(t, tt.expectedURL, baseURL)
assert.Equal(t, tt.expectedMethod, req.Method)
if tt.checkQuery != nil {
for key, expectedValues := range tt.checkQuery {
actualValue := req.URL.Query()[key]
assert.NotZero(t, len(actualValue))
for _, expectedValue := range expectedValues {
assert.Contains(t, actualValue, expectedValue, "Query parameter %s", key)
}
}
}
if tt.checkHeaders != nil {
for key, expectedValue := range tt.checkHeaders {
actualValue := req.Header.Get(key)
assert.Equal(t, expectedValue, actualValue, "Header %s", key)
}
}
if tt.params.Body != nil {
bodyBytes, err := io.ReadAll(req.Body)
require.NoError(t, err)
expectedContent := ""
switch tt.name {
case "request with body":
expectedContent = `{"test": "data"}`
case "complex request with all parameters":
expectedContent = `{"query": "stats.*"}`
}
assert.Equal(t, expectedContent, string(bodyBytes))
}
})
}
}
func Test_CreateRequest_Body(t *testing.T) {
ctx := context.Background()
service := &Service{}
dsInfo := &datasourceInfo{URL: "http://graphite.example.com"}
t.Run("string reader body", func(t *testing.T) {
bodyContent := `{"query": "stats.*", "format": "json"}`
params := URLParams{
Method: "POST",
Body: strings.NewReader(bodyContent),
}
req, err := service.createRequest(ctx, dsInfo, params)
require.NoError(t, err)
// Read the body to verify content
bodyBytes, err := io.ReadAll(req.Body)
require.NoError(t, err)
assert.Equal(t, bodyContent, string(bodyBytes))
})
t.Run("nil body", func(t *testing.T) {
params := URLParams{
Method: "GET",
Body: nil,
}
req, err := service.createRequest(ctx, dsInfo, params)
require.NoError(t, err)
assert.Nil(t, req.Body)
})
t.Run("empty body reader", func(t *testing.T) {
params := URLParams{
Method: "POST",
Body: strings.NewReader(""),
}
req, err := service.createRequest(ctx, dsInfo, params)
require.NoError(t, err)
bodyBytes, err := io.ReadAll(req.Body)
require.NoError(t, err)
assert.Empty(t, string(bodyBytes))
})
}