This adds provisioning endpoints for downloading alert rules and alert rule groups in a format that is compatible with file provisioning. Each endpoint supports both json and yaml response types via Accept header as well as a query parameter download=true/false that will set Content-Disposition to recommend initiating a download or inline display. This also makes some package changes to keep structs with potential to drift closer together. Eventually, other alerting file structs should also move into this new file package, but the rest require some refactoring that is out of scope for this PR.
1075 lines
43 KiB
Go
1075 lines
43 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
"time"
|
|
|
|
prometheus "github.com/prometheus/alertmanager/config"
|
|
"github.com/prometheus/alertmanager/timeinterval"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
|
"github.com/grafana/grafana/pkg/services/secrets"
|
|
secrets_fakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/web"
|
|
)
|
|
|
|
func TestProvisioningApi(t *testing.T) {
|
|
t.Run("policies", func(t *testing.T) {
|
|
t.Run("successful GET returns 200", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
|
|
response := sut.RouteGetPolicyTree(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
})
|
|
|
|
t.Run("successful PUT returns 202", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
tree := definitions.Route{}
|
|
|
|
response := sut.RoutePutPolicyTree(&rc, tree)
|
|
|
|
require.Equal(t, 202, response.Status())
|
|
})
|
|
|
|
t.Run("successful DELETE returns 202", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
|
|
response := sut.RouteResetPolicyTree(&rc)
|
|
|
|
require.Equal(t, 202, response.Status())
|
|
})
|
|
|
|
t.Run("when new policy tree is invalid", func(t *testing.T) {
|
|
t.Run("PUT returns 400", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
sut.policies = &fakeRejectingNotificationPolicyService{}
|
|
rc := createTestRequestCtx()
|
|
tree := definitions.Route{}
|
|
|
|
response := sut.RoutePutPolicyTree(&rc, tree)
|
|
|
|
require.Equal(t, 400, response.Status())
|
|
expBody := `{"error":"invalid object specification: invalid policy tree","message":"invalid object specification: invalid policy tree"}`
|
|
require.Equal(t, expBody, string(response.Body()))
|
|
})
|
|
})
|
|
|
|
t.Run("when org has no AM config", func(t *testing.T) {
|
|
t.Run("GET returns 404", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
rc.SignedInUser.OrgID = 2
|
|
|
|
response := sut.RouteGetPolicyTree(&rc)
|
|
|
|
require.Equal(t, 404, response.Status())
|
|
})
|
|
|
|
t.Run("POST returns 404", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
rc.SignedInUser.OrgID = 2
|
|
|
|
response := sut.RouteGetPolicyTree(&rc)
|
|
|
|
require.Equal(t, 404, response.Status())
|
|
})
|
|
})
|
|
|
|
t.Run("when an unspecified error occurrs", func(t *testing.T) {
|
|
t.Run("GET returns 500", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
sut.policies = &fakeFailingNotificationPolicyService{}
|
|
rc := createTestRequestCtx()
|
|
|
|
response := sut.RouteGetPolicyTree(&rc)
|
|
|
|
require.Equal(t, 500, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "something went wrong")
|
|
})
|
|
|
|
t.Run("PUT returns 500", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
sut.policies = &fakeFailingNotificationPolicyService{}
|
|
rc := createTestRequestCtx()
|
|
tree := definitions.Route{}
|
|
|
|
response := sut.RoutePutPolicyTree(&rc, tree)
|
|
|
|
require.Equal(t, 500, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "something went wrong")
|
|
})
|
|
|
|
t.Run("DELETE returns 500", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
sut.policies = &fakeFailingNotificationPolicyService{}
|
|
rc := createTestRequestCtx()
|
|
|
|
response := sut.RouteResetPolicyTree(&rc)
|
|
|
|
require.Equal(t, 500, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "something went wrong")
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("contact points", func(t *testing.T) {
|
|
t.Run("are invalid", func(t *testing.T) {
|
|
t.Run("POST returns 400", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
cp := createInvalidContactPoint()
|
|
|
|
response := sut.RoutePostContactPoint(&rc, cp)
|
|
|
|
require.Equal(t, 400, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "recipient must be specified")
|
|
})
|
|
|
|
t.Run("PUT returns 400", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
cp := createInvalidContactPoint()
|
|
|
|
response := sut.RoutePutContactPoint(&rc, cp, "email-uid")
|
|
|
|
require.Equal(t, 400, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "recipient must be specified")
|
|
})
|
|
})
|
|
|
|
t.Run("are missing, PUT returns 404", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
cp := createInvalidContactPoint()
|
|
|
|
response := sut.RoutePutContactPoint(&rc, cp, "does not exist")
|
|
|
|
require.Equal(t, 404, response.Status())
|
|
})
|
|
})
|
|
|
|
t.Run("templates", func(t *testing.T) {
|
|
t.Run("are invalid", func(t *testing.T) {
|
|
t.Run("PUT returns 400", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
tmpl := definitions.NotificationTemplateContent{Template: ""}
|
|
|
|
response := sut.RoutePutTemplate(&rc, tmpl, "test")
|
|
|
|
require.Equal(t, 400, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "template must have content")
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("mute timings", func(t *testing.T) {
|
|
t.Run("are invalid", func(t *testing.T) {
|
|
t.Run("POST returns 400", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
mti := createInvalidMuteTiming()
|
|
|
|
response := sut.RoutePostMuteTiming(&rc, mti)
|
|
|
|
require.Equal(t, 400, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "invalid")
|
|
})
|
|
|
|
t.Run("PUT returns 400", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
mti := createInvalidMuteTiming()
|
|
|
|
response := sut.RoutePutMuteTiming(&rc, mti, "interval")
|
|
|
|
require.Equal(t, 400, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "invalid")
|
|
})
|
|
})
|
|
|
|
t.Run("are missing, PUT returns 404", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
mti := definitions.MuteTimeInterval{}
|
|
|
|
response := sut.RoutePutMuteTiming(&rc, mti, "does not exist")
|
|
|
|
require.Equal(t, 404, response.Status())
|
|
})
|
|
})
|
|
|
|
t.Run("alert rules", func(t *testing.T) {
|
|
t.Run("are invalid", func(t *testing.T) {
|
|
t.Run("POST returns 400 on wrong body params", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
rule := createInvalidAlertRule()
|
|
|
|
response := sut.RoutePostAlertRule(&rc, rule)
|
|
|
|
require.Equal(t, 400, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "invalid alert rule")
|
|
})
|
|
|
|
t.Run("PUT returns 400 on wrong body params", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
uid := "123123"
|
|
rule := createTestAlertRule("rule", 1)
|
|
rule.UID = uid
|
|
insertRule(t, sut, rule)
|
|
rule = createInvalidAlertRule()
|
|
|
|
response := sut.RoutePutAlertRule(&rc, rule, uid)
|
|
require.Equal(t, 400, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "invalid alert rule")
|
|
})
|
|
})
|
|
|
|
t.Run("exist in non-default orgs", func(t *testing.T) {
|
|
t.Run("POST sets expected fields with no provenance", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
rc.Req.Header = map[string][]string{"X-Disable-Provenance": {"true"}}
|
|
rc.OrgID = 3
|
|
rule := createTestAlertRule("rule", 1)
|
|
|
|
response := sut.RoutePostAlertRule(&rc, rule)
|
|
|
|
require.Equal(t, 201, response.Status())
|
|
created := deserializeRule(t, response.Body())
|
|
require.Equal(t, int64(3), created.OrgID)
|
|
require.Equal(t, models.ProvenanceNone, created.Provenance)
|
|
})
|
|
|
|
t.Run("PUT sets expected fields with no provenance", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
uid := t.Name()
|
|
rule := createTestAlertRule("rule", 1)
|
|
rule.UID = uid
|
|
insertRuleInOrg(t, sut, rule, 3)
|
|
rc := createTestRequestCtx()
|
|
rc.Req.Header = map[string][]string{"X-Disable-Provenance": {"hello"}}
|
|
rc.OrgID = 3
|
|
rule.OrgID = 1 // Set the org back to something wrong, we should still prefer the value from the req context.
|
|
|
|
response := sut.RoutePutAlertRule(&rc, rule, rule.UID)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
created := deserializeRule(t, response.Body())
|
|
require.Equal(t, int64(3), created.OrgID)
|
|
require.Equal(t, models.ProvenanceNone, created.Provenance)
|
|
})
|
|
})
|
|
|
|
t.Run("are missing, PUT returns 404", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
rule := createTestAlertRule("rule", 1)
|
|
|
|
response := sut.RoutePutAlertRule(&rc, rule, "does not exist")
|
|
|
|
require.Equal(t, 404, response.Status())
|
|
})
|
|
|
|
t.Run("have reached the rule quota, POST returns 403", func(t *testing.T) {
|
|
env := createTestEnv(t)
|
|
quotas := provisioning.MockQuotaChecker{}
|
|
quotas.EXPECT().LimitExceeded()
|
|
env.quotas = "as
|
|
sut := createProvisioningSrvSutFromEnv(t, &env)
|
|
rule := createTestAlertRule("rule", 1)
|
|
rc := createTestRequestCtx()
|
|
|
|
response := sut.RoutePostAlertRule(&rc, rule)
|
|
|
|
require.Equal(t, 403, response.Status())
|
|
})
|
|
})
|
|
|
|
t.Run("alert rule groups", func(t *testing.T) {
|
|
t.Run("are present, GET returns 200", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "my-cool-group")
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
})
|
|
|
|
t.Run("are missing, GET returns 404", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "does not exist")
|
|
|
|
require.Equal(t, 404, response.Status())
|
|
})
|
|
|
|
t.Run("are invalid at group level", func(t *testing.T) {
|
|
t.Run("PUT returns 400", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
group := createInvalidAlertRuleGroup()
|
|
group.Interval = 0
|
|
|
|
response := sut.RoutePutAlertRuleGroup(&rc, group, "folder-uid", group.Title)
|
|
|
|
require.Equal(t, 400, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "invalid alert rule")
|
|
})
|
|
})
|
|
|
|
t.Run("are invalid at rule level", func(t *testing.T) {
|
|
t.Run("PUT returns 400", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
group := createInvalidAlertRuleGroup()
|
|
|
|
response := sut.RoutePutAlertRuleGroup(&rc, group, "folder-uid", group.Title)
|
|
|
|
require.Equal(t, 400, response.Status())
|
|
require.NotEmpty(t, response.Body())
|
|
require.Contains(t, string(response.Body()), "invalid alert rule")
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("exports", func(t *testing.T) {
|
|
t.Run("alert rule group", func(t *testing.T) {
|
|
t.Run("are present, GET returns 200", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
})
|
|
|
|
t.Run("are missing, GET returns 404", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "does not exist")
|
|
|
|
require.Equal(t, 404, response.Status())
|
|
})
|
|
|
|
t.Run("accept header contains yaml, GET returns text yaml", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type"))
|
|
})
|
|
|
|
t.Run("accept header contains json, GET returns json", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/json")
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
|
})
|
|
|
|
t.Run("accept header contains json and yaml, GET returns json", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/json, application/yaml")
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
|
})
|
|
|
|
t.Run("query param download=true, GET returns content disposition attachment", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Form.Set("download", "true")
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Contains(t, rc.Context.Resp.Header().Get("Content-Disposition"), "attachment")
|
|
})
|
|
|
|
t.Run("query param download=false, GET returns empty content disposition", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Form.Set("download", "false")
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
|
})
|
|
|
|
t.Run("query param download not set, GET returns empty content disposition", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
|
})
|
|
|
|
t.Run("json body content is as expected", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
insertRule(t, sut, createTestAlertRule("rule2", 1))
|
|
|
|
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"},{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]}]}`
|
|
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, expectedResponse, string(response.Body()))
|
|
})
|
|
|
|
t.Run("yaml body content is as expected", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
insertRule(t, sut, createTestAlertRule("rule2", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
|
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n"
|
|
|
|
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, expectedResponse, string(response.Body()))
|
|
})
|
|
})
|
|
|
|
t.Run("alert rule", func(t *testing.T) {
|
|
t.Run("are present, GET returns 200", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
})
|
|
|
|
t.Run("are missing, GET returns 404", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule404")
|
|
|
|
require.Equal(t, 404, response.Status())
|
|
})
|
|
|
|
t.Run("accept header contains yaml, GET returns text yaml", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type"))
|
|
})
|
|
|
|
t.Run("accept header contains json, GET returns json", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/json")
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
|
})
|
|
|
|
t.Run("accept header contains json and yaml, GET returns json", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/json, application/yaml")
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
|
})
|
|
|
|
t.Run("query param download=true, GET returns content disposition attachment", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
rc.Context.Req.Form.Set("download", "true")
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Contains(t, rc.Context.Resp.Header().Get("Content-Disposition"), "attachment")
|
|
})
|
|
|
|
t.Run("query param download=false, GET returns empty content disposition", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
rc.Context.Req.Form.Set("download", "false")
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
|
})
|
|
|
|
t.Run("query param download not set, GET returns empty content disposition", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
|
})
|
|
|
|
t.Run("json body content is as expected", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]}]}`
|
|
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, expectedResponse, string(response.Body()))
|
|
})
|
|
|
|
t.Run("yaml body content is as expected", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
|
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n"
|
|
|
|
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, expectedResponse, string(response.Body()))
|
|
})
|
|
})
|
|
|
|
t.Run("all alert rules", func(t *testing.T) {
|
|
t.Run("are present, GET returns 200", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
response := sut.RouteGetAlertRulesExport(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
})
|
|
|
|
t.Run("accept header contains yaml, GET returns text yaml", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
|
response := sut.RouteGetAlertRulesExport(&rc)
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type"))
|
|
})
|
|
|
|
t.Run("accept header contains json, GET returns json", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/json")
|
|
response := sut.RouteGetAlertRulesExport(&rc)
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
|
})
|
|
|
|
t.Run("accept header contains json and yaml, GET returns json", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/json, application/yaml")
|
|
response := sut.RouteGetAlertRulesExport(&rc)
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
|
})
|
|
|
|
t.Run("query param download=true, GET returns content disposition attachment", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Form.Set("download", "true")
|
|
response := sut.RouteGetAlertRulesExport(&rc)
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Contains(t, rc.Context.Resp.Header().Get("Content-Disposition"), "attachment")
|
|
})
|
|
|
|
t.Run("query param download=false, GET returns empty content disposition", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
rc.Context.Req.Form.Set("download", "false")
|
|
response := sut.RouteGetAlertRulesExport(&rc)
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
|
})
|
|
|
|
t.Run("query param download not set, GET returns empty content disposition", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRule("rule", 1))
|
|
|
|
response := sut.RouteGetAlertRulesExport(&rc)
|
|
response.WriteTo(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
|
})
|
|
|
|
t.Run("json body content is as expected", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule1", 1, "folder-uid", "groupa"))
|
|
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule2", 1, "folder-uid", "groupb"))
|
|
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule3", 1, "folder-uid2", "groupb"))
|
|
|
|
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]},{"orgId":1,"name":"groupb","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]},{"orgId":1,"name":"groupb","folder":"Folder Title2","interval":"1m","rules":[{"uid":"rule3","title":"rule3","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]}]}`
|
|
|
|
response := sut.RouteGetAlertRulesExport(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, expectedResponse, string(response.Body()))
|
|
})
|
|
|
|
t.Run("yaml body content is as expected", func(t *testing.T) {
|
|
sut := createProvisioningSrvSut(t)
|
|
rc := createTestRequestCtx()
|
|
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule1", 1, "folder-uid", "groupa"))
|
|
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule2", 1, "folder-uid", "groupb"))
|
|
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule3", 1, "folder-uid2", "groupb"))
|
|
|
|
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
|
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: groupa\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n - orgId: 1\n name: groupb\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n - orgId: 1\n name: groupb\n folder: Folder Title2\n interval: 1m\n rules:\n - uid: rule3\n title: rule3\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n"
|
|
|
|
response := sut.RouteGetAlertRulesExport(&rc)
|
|
|
|
require.Equal(t, 200, response.Status())
|
|
require.Equal(t, expectedResponse, string(response.Body()))
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
// testEnvironment binds together common dependencies for testing alerting APIs.
|
|
type testEnvironment struct {
|
|
secrets secrets.Service
|
|
log log.Logger
|
|
store store.DBstore
|
|
dashboardService dashboards.DashboardService
|
|
configs provisioning.AMConfigStore
|
|
xact provisioning.TransactionManager
|
|
quotas provisioning.QuotaChecker
|
|
prov provisioning.ProvisioningStore
|
|
}
|
|
|
|
func createTestEnv(t *testing.T) testEnvironment {
|
|
t.Helper()
|
|
|
|
secrets := secrets_fakes.NewFakeSecretsService()
|
|
log := log.NewNopLogger()
|
|
configs := &provisioning.MockAMConfigStore{}
|
|
configs.EXPECT().
|
|
GetsConfig(models.AlertConfiguration{
|
|
AlertmanagerConfiguration: testConfig,
|
|
})
|
|
sqlStore := db.InitTestDB(t)
|
|
store := store.DBstore{
|
|
SQLStore: sqlStore,
|
|
Cfg: setting.UnifiedAlertingSettings{
|
|
BaseInterval: time.Second * 10,
|
|
},
|
|
}
|
|
quotas := &provisioning.MockQuotaChecker{}
|
|
quotas.EXPECT().LimitOK()
|
|
xact := &provisioning.NopTransactionManager{}
|
|
prov := &provisioning.MockProvisioningStore{}
|
|
prov.EXPECT().SaveSucceeds()
|
|
prov.EXPECT().GetReturns(models.ProvenanceNone)
|
|
|
|
dashboardService := dashboards.NewFakeDashboardService(t)
|
|
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(&dashboards.Dashboard{
|
|
UID: "folder-uid",
|
|
Title: "Folder Title",
|
|
}, nil).Maybe()
|
|
dashboardService.On("GetDashboards", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardsQuery")).Return([]*dashboards.Dashboard{{
|
|
UID: "folder-uid",
|
|
Title: "Folder Title",
|
|
},
|
|
{
|
|
UID: "folder-uid2",
|
|
Title: "Folder Title2",
|
|
}}, nil).Maybe()
|
|
|
|
return testEnvironment{
|
|
secrets: secrets,
|
|
log: log,
|
|
configs: configs,
|
|
store: store,
|
|
dashboardService: dashboardService,
|
|
xact: xact,
|
|
prov: prov,
|
|
quotas: quotas,
|
|
}
|
|
}
|
|
|
|
func createProvisioningSrvSut(t *testing.T) ProvisioningSrv {
|
|
t.Helper()
|
|
|
|
env := createTestEnv(t)
|
|
return createProvisioningSrvSutFromEnv(t, &env)
|
|
}
|
|
|
|
func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) ProvisioningSrv {
|
|
t.Helper()
|
|
|
|
return ProvisioningSrv{
|
|
log: env.log,
|
|
policies: newFakeNotificationPolicyService(),
|
|
contactPointService: provisioning.NewContactPointService(env.configs, env.secrets, env.prov, env.xact, env.log),
|
|
templates: provisioning.NewTemplateService(env.configs, env.prov, env.xact, env.log),
|
|
muteTimings: provisioning.NewMuteTimingService(env.configs, env.prov, env.xact, env.log),
|
|
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.dashboardService, env.quotas, env.xact, 60, 10, env.log),
|
|
}
|
|
}
|
|
|
|
func createTestRequestCtx() contextmodel.ReqContext {
|
|
return contextmodel.ReqContext{
|
|
Context: &web.Context{
|
|
Req: &http.Request{
|
|
Header: make(http.Header),
|
|
Form: make(url.Values),
|
|
},
|
|
Resp: web.NewResponseWriter("GET", httptest.NewRecorder()),
|
|
},
|
|
SignedInUser: &user.SignedInUser{
|
|
OrgID: 1,
|
|
},
|
|
}
|
|
}
|
|
|
|
type fakeNotificationPolicyService struct {
|
|
tree definitions.Route
|
|
prov models.Provenance
|
|
}
|
|
|
|
func newFakeNotificationPolicyService() *fakeNotificationPolicyService {
|
|
return &fakeNotificationPolicyService{
|
|
tree: definitions.Route{
|
|
Receiver: "some-receiver",
|
|
},
|
|
prov: models.ProvenanceNone,
|
|
}
|
|
}
|
|
|
|
func (f *fakeNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
|
if orgID != 1 {
|
|
return definitions.Route{}, store.ErrNoAlertmanagerConfiguration
|
|
}
|
|
result := f.tree
|
|
result.Provenance = f.prov
|
|
return result, nil
|
|
}
|
|
|
|
func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance) error {
|
|
if orgID != 1 {
|
|
return store.ErrNoAlertmanagerConfiguration
|
|
}
|
|
f.tree = tree
|
|
f.prov = p
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
|
f.tree = definitions.Route{} // TODO
|
|
return f.tree, nil
|
|
}
|
|
|
|
type fakeFailingNotificationPolicyService struct{}
|
|
|
|
func (f *fakeFailingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
|
return definitions.Route{}, fmt.Errorf("something went wrong")
|
|
}
|
|
|
|
func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance) error {
|
|
return fmt.Errorf("something went wrong")
|
|
}
|
|
|
|
func (f *fakeFailingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
|
return definitions.Route{}, fmt.Errorf("something went wrong")
|
|
}
|
|
|
|
type fakeRejectingNotificationPolicyService struct{}
|
|
|
|
func (f *fakeRejectingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
|
return definitions.Route{}, nil
|
|
}
|
|
|
|
func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance) error {
|
|
return fmt.Errorf("%w: invalid policy tree", provisioning.ErrValidation)
|
|
}
|
|
|
|
func (f *fakeRejectingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
|
return definitions.Route{}, nil
|
|
}
|
|
|
|
func createInvalidContactPoint() definitions.EmbeddedContactPoint {
|
|
settings, _ := simplejson.NewJson([]byte(`{}`))
|
|
return definitions.EmbeddedContactPoint{
|
|
Name: "test-contact-point",
|
|
Type: "slack",
|
|
Settings: settings,
|
|
}
|
|
}
|
|
|
|
func createInvalidMuteTiming() definitions.MuteTimeInterval {
|
|
return definitions.MuteTimeInterval{
|
|
MuteTimeInterval: prometheus.MuteTimeInterval{
|
|
Name: "interval",
|
|
TimeIntervals: []timeinterval.TimeInterval{
|
|
{
|
|
Weekdays: []timeinterval.WeekdayRange{
|
|
{
|
|
InclusiveRange: timeinterval.InclusiveRange{
|
|
Begin: -1,
|
|
End: 7,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func createInvalidAlertRule() definitions.ProvisionedAlertRule {
|
|
return definitions.ProvisionedAlertRule{}
|
|
}
|
|
|
|
func createInvalidAlertRuleGroup() definitions.AlertRuleGroup {
|
|
return definitions.AlertRuleGroup{
|
|
Title: "invalid",
|
|
Interval: 10,
|
|
Rules: []definitions.ProvisionedAlertRule{{}},
|
|
}
|
|
}
|
|
|
|
func createTestAlertRuleWithFolderAndGroup(title string, orgID int64, folderUid string, group string) definitions.ProvisionedAlertRule {
|
|
rule := createTestAlertRule(title, orgID)
|
|
rule.FolderUID = folderUid
|
|
rule.RuleGroup = group
|
|
return rule
|
|
}
|
|
|
|
func createTestAlertRule(title string, orgID int64) definitions.ProvisionedAlertRule {
|
|
return definitions.ProvisionedAlertRule{
|
|
UID: title,
|
|
OrgID: orgID,
|
|
Title: title,
|
|
Condition: "A",
|
|
Data: []models.AlertQuery{
|
|
{
|
|
RefID: "A",
|
|
Model: json.RawMessage(testModel),
|
|
RelativeTimeRange: models.RelativeTimeRange{
|
|
From: models.Duration(60),
|
|
To: models.Duration(0),
|
|
},
|
|
},
|
|
},
|
|
RuleGroup: "my-cool-group",
|
|
FolderUID: "folder-uid",
|
|
For: model.Duration(60),
|
|
NoDataState: models.OK,
|
|
ExecErrState: models.OkErrState,
|
|
}
|
|
}
|
|
|
|
func insertRule(t *testing.T, srv ProvisioningSrv, rule definitions.ProvisionedAlertRule) {
|
|
insertRuleInOrg(t, srv, rule, 1)
|
|
}
|
|
|
|
func insertRuleInOrg(t *testing.T, srv ProvisioningSrv, rule definitions.ProvisionedAlertRule, orgID int64) {
|
|
t.Helper()
|
|
|
|
rc := createTestRequestCtx()
|
|
rc.OrgID = orgID
|
|
resp := srv.RoutePostAlertRule(&rc, rule)
|
|
require.Equal(t, 201, resp.Status())
|
|
}
|
|
|
|
func deserializeRule(t *testing.T, data []byte) definitions.ProvisionedAlertRule {
|
|
t.Helper()
|
|
|
|
var rule definitions.ProvisionedAlertRule
|
|
err := json.Unmarshal(data, &rule)
|
|
require.NoError(t, err)
|
|
return rule
|
|
}
|
|
|
|
var testModel = `
|
|
{
|
|
"conditions": [
|
|
{
|
|
"evaluator": {
|
|
"params": [
|
|
3
|
|
],
|
|
"type": "gt"
|
|
},
|
|
"operator": {
|
|
"type": "and"
|
|
},
|
|
"query": {
|
|
"params": [
|
|
"A"
|
|
]
|
|
},
|
|
"reducer": {
|
|
"type": "last"
|
|
},
|
|
"type": "query"
|
|
}
|
|
],
|
|
"datasource": {
|
|
"type": "__expr__",
|
|
"uid": "-100"
|
|
},
|
|
"expression": "1==0",
|
|
"intervalMs": 1000,
|
|
"maxDataPoints": 43200,
|
|
"refId": "A",
|
|
"type": "math"
|
|
}
|
|
`
|
|
|
|
var testConfig = `
|
|
{
|
|
"template_files": {
|
|
"a": "template"
|
|
},
|
|
"alertmanager_config": {
|
|
"route": {
|
|
"receiver": "grafana-default-email"
|
|
},
|
|
"receivers": [{
|
|
"name": "grafana-default-email",
|
|
"grafana_managed_receiver_configs": [{
|
|
"uid": "email-uid",
|
|
"name": "email receiver",
|
|
"type": "email",
|
|
"isDefault": true,
|
|
"settings": {
|
|
"addresses": "<example@email.com>"
|
|
}
|
|
}]
|
|
}],
|
|
"mute_time_intervals": [{
|
|
"name": "interval",
|
|
"time_intervals": []
|
|
}]
|
|
}
|
|
}
|
|
`
|