The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/expr/ml/node_test.go

167 lines
4.7 KiB

package ml
import (
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func TestUnmarshalCommand(t *testing.T) {
appURL := "https://grafana.com"
updateJson := func(cmd string, f func(m map[string]interface{})) func(t *testing.T) []byte {
return func(t *testing.T) []byte {
var d map[string]interface{}
require.NoError(t, json.UnmarshalFromString(cmd, &d))
f(d)
data, err := json.Marshal(d)
require.NoError(t, err)
return data
}
}
t.Run("should parse outlier command", func(t *testing.T) {
cmd, err := UnmarshalCommand([]byte(outlierQuery), appURL)
require.NoError(t, err)
require.IsType(t, &OutlierCommand{}, cmd)
outlier := cmd.(*OutlierCommand)
require.Equal(t, 1234*time.Millisecond, outlier.interval)
require.Equal(t, appURL, outlier.appURL)
require.Equal(t, OutlierCommandConfiguration{
DatasourceType: "prometheus",
DatasourceUID: "a4ce599c-4c93-44b9-be5b-76385b8c01be",
QueryParams: map[string]interface{}{
"expr": "go_goroutines{}",
"range": true,
"refId": "A",
},
Algorithm: map[string]interface{}{
"name": "dbscan",
"config": map[string]interface{}{
"epsilon": 7.667,
},
"sensitivity": 0.83,
},
ResponseType: "binary",
}, outlier.config)
})
t.Run("should fallback to default if 'intervalMs' is not specified", func(t *testing.T) {
data := updateJson(outlierQuery, func(m map[string]interface{}) {
delete(m, "intervalMs")
})(t)
cmd, err := UnmarshalCommand(data, appURL)
require.NoError(t, err)
outlier := cmd.(*OutlierCommand)
require.Equal(t, defaultInterval, outlier.interval)
})
t.Run("fails when", func(t *testing.T) {
testCases := []struct {
name string
config func(t *testing.T) []byte
err string
}{
{
name: "field 'type' is missing",
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
delete(cmd, "type")
}),
err: "required field 'type' is not specified or empty. Should be one of [outlier]",
},
{
name: "field 'type' is not known",
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
cmd["type"] = uuid.NewString()
}),
err: "unsupported command type. Should be one of [outlier]",
},
{
name: "field 'type' is not string",
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
cmd["type"] = map[string]interface{}{
"data": 1,
}
}),
err: "failed to unmarshal Machine learning command",
},
{
name: "field 'config' is missing",
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
delete(cmd, "config")
}),
err: "required field 'config' is not specified",
},
{
name: "field 'intervalMs' is not number",
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
cmd["intervalMs"] = "test"
}),
err: "failed to unmarshal Machine learning command",
},
{
name: "field 'config.datasource_uid' is not specified",
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
cfg := cmd["config"].(map[string]interface{})
delete(cfg, "datasource_uid")
}),
err: "required field `config.datasource_uid` is not specified",
},
{
name: "field 'config.algorithm' is not specified",
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
cfg := cmd["config"].(map[string]interface{})
delete(cfg, "algorithm")
}),
err: "required field `config.algorithm` is not specified",
},
{
name: "field 'config.response_type' is not specified",
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
cfg := cmd["config"].(map[string]interface{})
delete(cfg, "response_type")
}),
err: "required field `config.response_type` is not specified",
},
{
name: "fields 'config.query' and 'config.query_params' are not specified",
config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
cfg := cmd["config"].(map[string]interface{})
delete(cfg, "query")
delete(cfg, "query_params")
}),
err: "neither of required fields `config.query_params` or `config.query` are specified",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
_, err := UnmarshalCommand(testCase.config(t), appURL)
require.ErrorContains(t, err, testCase.err)
})
}
})
}
const outlierQuery = `
{
"type": "outlier",
"intervalMs": 1234,
"config": {
"datasource_uid": "a4ce599c-4c93-44b9-be5b-76385b8c01be",
"datasource_type": "prometheus",
"query_params": {
"expr": "go_goroutines{}",
"range": true,
"refId": "A"
},
"response_type": "binary",
"algorithm": {
"name": "dbscan",
"config": {
"epsilon": 7.667
},
"sensitivity": 0.83
}
}
}
`