diff --git a/internal/checks/base_test.go b/internal/checks/base_test.go index 46e1c23e..897c6577 100644 --- a/internal/checks/base_test.go +++ b/internal/checks/base_test.go @@ -76,6 +76,25 @@ func simpleProm(name, uri string, timeout time.Duration, required bool) *promapi ) } +func doubleProm(name, uri string, timeout time.Duration, required bool) *promapi.FailoverGroup { + return promapi.NewFailoverGroup( + name, + []*promapi.Prometheus{ + promapi.NewPrometheus(name, uri, map[string]string{"X-Debug": "1"}, timeout, 16, 1000, nil), + promapi.NewPrometheus(name+"2", uri, map[string]string{"X-Debug": "1"}, timeout, 16, 1000, nil), + }, + required, + "up", + nil, + nil, + nil, + ) +} + +func newDoubleProm(uri string) *promapi.FailoverGroup { + return doubleProm("prom", uri, time.Second*5, true) +} + func newSimpleProm(uri string) *promapi.FailoverGroup { return simpleProm("prom", uri, time.Second*5, true) } diff --git a/internal/checks/promql_counter_test.go b/internal/checks/promql_counter_test.go index 06c1c30d..879048e3 100644 --- a/internal/checks/promql_counter_test.go +++ b/internal/checks/promql_counter_test.go @@ -11,6 +11,30 @@ import ( "github.com/cloudflare/pint/internal/promapi" ) +var ( + respondWithInternalErrorThenGoodData func(writer responseWriter) responseWriter + respondWithEmptyDataThenGoodData func(writer responseWriter) responseWriter +) + +func init() { + respondWithInternalErrorThenGoodDataState := 0 + respondWithInternalErrorThenGoodData = func(goodResponse responseWriter) responseWriter { + respondWithInternalErrorThenGoodDataState++ + if respondWithInternalErrorThenGoodDataState == 0 { + return promError{code: 500, errorType: v1.ErrServer, err: "internal error"} + } + return goodResponse + } + respondWithEmptyDataThenGoodDataState := 0 + respondWithEmptyDataThenGoodData = func(goodResponse responseWriter) responseWriter { + respondWithEmptyDataThenGoodDataState++ + if respondWithEmptyDataThenGoodDataState == 0 { + return metadataResponse{metadata: map[string][]v1.Metadata{}} + } + return goodResponse + } +} + func newCounterCheck(prom *promapi.FailoverGroup) checks.RuleChecker { return checks.NewCounterCheck(prom) } @@ -202,6 +226,19 @@ func TestCounterCheck(t *testing.T) { }, }, }, + { + description: "empty data from Prometheus API", + content: "- alert: my alert\n expr: irate(foo[5m])\n", + checker: newCounterCheck, + prometheus: newSimpleProm, + problems: noProblems, + mocks: []*prometheusMock{ + { + conds: []requestCondition{requireMetadataPath}, + resp: metadataResponse{metadata: map[string][]v1.Metadata{}}, + }, + }, + }, { description: "500 error from Prometheus API", content: "- record: foo\n expr: rate(foo[5m])\n", @@ -225,6 +262,48 @@ func TestCounterCheck(t *testing.T) { }, }, }, + { + description: "500 error from first Prometheus API - use counter with rate", + content: "- record: foo\n expr: rate(foo[5m])\n", + checker: newCounterCheck, + prometheus: newDoubleProm, + problems: noProblems, + + mocks: []*prometheusMock{ + { + conds: []requestCondition{requireMetadataPath}, + resp: respondWithInternalErrorThenGoodData(metadataResponse{metadata: map[string][]v1.Metadata{ + "foo": {{Type: "counter"}}, + }}), + }, + }, + }, + { + description: "empty data from first Prometheus API - use counter with delta", + content: "- record: foo\n expr: delta(foo[5m])\n", + checker: newCounterCheck, + prometheus: newDoubleProm, + problems: func(uri string) []checks.Problem { + return []checks.Problem{ + { + Fragment: "foo", + Lines: []int{2}, + Reporter: "promql/counter", + Text: CounterMustUseFuncTextForRecordingRule("foo"), + Severity: checks.Warning, + }, + } + }, + + mocks: []*prometheusMock{ + { + conds: []requestCondition{requireMetadataPath}, + resp: respondWithEmptyDataThenGoodData(metadataResponse{metadata: map[string][]v1.Metadata{ + "foo": {{Type: "counter"}}, + }}), + }, + }, + }, } runTests(t, testCases)