mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add integration tests for Rule Export API (#75896)
This commit is contained in:
parent
51499d7763
commit
a66760f9f2
@ -2,14 +2,18 @@ package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -30,18 +34,21 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
//go:embed test-data/*.*
|
||||
var testData embed.FS
|
||||
|
||||
func TestIntegrationAlertRulePermissions(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, p)
|
||||
permissionsStore := resourcepermissions.NewStore(store, featuremgmt.WithFeatures())
|
||||
|
||||
// Create a user to make authenticated requests
|
||||
@ -58,231 +65,264 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
|
||||
// Create the namespace we'll save our alerts to.
|
||||
apiClient.CreateFolder(t, "folder2", "folder2")
|
||||
|
||||
postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json"))
|
||||
require.NoError(t, err)
|
||||
var group1 apimodels.PostableRuleGroupConfig
|
||||
require.NoError(t, json.Unmarshal(postGroupRaw, &group1))
|
||||
|
||||
// Create rule under folder1
|
||||
createRule(t, apiClient, "folder1")
|
||||
status, response := apiClient.PostRulesGroup(t, "folder1", &group1)
|
||||
require.Equalf(t, http.StatusAccepted, status, response)
|
||||
|
||||
postGroupRaw, err = testData.ReadFile(path.Join("test-data", "rulegroup-2-post.json"))
|
||||
require.NoError(t, err)
|
||||
var group2 apimodels.PostableRuleGroupConfig
|
||||
require.NoError(t, json.Unmarshal(postGroupRaw, &group2))
|
||||
|
||||
// Create rule under folder2
|
||||
createRule(t, apiClient, "folder2")
|
||||
status, response = apiClient.PostRulesGroup(t, "folder2", &group2)
|
||||
require.Equalf(t, http.StatusAccepted, status, response)
|
||||
|
||||
// With the rules created, let's make sure that rule definitions are stored.
|
||||
{
|
||||
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(u)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
allRules, status, _ := apiClient.GetAllRulesWithStatus(t)
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
status, allExportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var allExport apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(allExportRaw), &allExport))
|
||||
|
||||
t.Run("when user has all permissions", func(t *testing.T) {
|
||||
t.Run("Get all returns all rules", func(t *testing.T) {
|
||||
var group1, group2 apimodels.GettableRuleGroupConfig
|
||||
|
||||
getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-get.json"))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup1Raw, &group1))
|
||||
getGroup2Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-2-get.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup2Raw, &group2))
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 200)
|
||||
|
||||
body, _ := rulesNamespaceWithoutVariableValues(t, b)
|
||||
expectedGetNamespaceResponseBody := `
|
||||
{
|
||||
"folder1":[
|
||||
{
|
||||
"name":"arulegroup",
|
||||
"interval":"1m",
|
||||
"rules":[
|
||||
{
|
||||
"annotations": {
|
||||
"annotation1": "val1"
|
||||
},
|
||||
"expr":"",
|
||||
"for": "2m",
|
||||
"labels": {
|
||||
"label1": "val1"
|
||||
},
|
||||
"grafana_alert":{
|
||||
"id":1,
|
||||
"orgId":1,
|
||||
"title":"rule under folder folder1",
|
||||
"condition":"A",
|
||||
"data":[
|
||||
{
|
||||
"refId":"A",
|
||||
"queryType":"",
|
||||
"relativeTimeRange":{
|
||||
"from":18000,
|
||||
"to":10800
|
||||
},
|
||||
"datasourceUid":"__expr__",
|
||||
"model":{
|
||||
"expression":"2 + 3 \u003E 1",
|
||||
"intervalMs":1000,
|
||||
"maxDataPoints":43200,
|
||||
"type":"math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated":"2021-02-21T01:10:30Z",
|
||||
"intervalSeconds":60,
|
||||
"is_paused":false,
|
||||
"version":1,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"namespace_id":1,
|
||||
"rule_group":"arulegroup",
|
||||
"no_data_state":"NoData",
|
||||
"exec_err_state":"Alerting"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"folder2":[
|
||||
{
|
||||
"name":"arulegroup",
|
||||
"interval":"1m",
|
||||
"rules":[
|
||||
{
|
||||
"annotations": {
|
||||
"annotation1": "val1"
|
||||
},
|
||||
"expr":"",
|
||||
"for": "2m",
|
||||
"labels": {
|
||||
"label1": "val1"
|
||||
},
|
||||
"grafana_alert":{
|
||||
"id":2,
|
||||
"orgId":1,
|
||||
"title":"rule under folder folder2",
|
||||
"condition":"A",
|
||||
"data":[
|
||||
{
|
||||
"refId":"A",
|
||||
"queryType":"",
|
||||
"relativeTimeRange":{
|
||||
"from":18000,
|
||||
"to":10800
|
||||
},
|
||||
"datasourceUid":"__expr__",
|
||||
"model":{
|
||||
"expression":"2 + 3 \u003E 1",
|
||||
"intervalMs":1000,
|
||||
"maxDataPoints":43200,
|
||||
"type":"math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated":"2021-02-21T01:10:30Z",
|
||||
"intervalSeconds":60,
|
||||
"is_paused":false,
|
||||
"version":1,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"namespace_id":2,
|
||||
"rule_group":"arulegroup",
|
||||
"no_data_state":"NoData",
|
||||
"exec_err_state":"Alerting"
|
||||
}
|
||||
}
|
||||
]
|
||||
expected := apimodels.NamespaceConfigResponse{
|
||||
"folder1": []apimodels.GettableRuleGroupConfig{
|
||||
group1,
|
||||
},
|
||||
"folder2": []apimodels.GettableRuleGroupConfig{
|
||||
group2,
|
||||
},
|
||||
}
|
||||
]
|
||||
}`
|
||||
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
|
||||
|
||||
pathsToIgnore := []string{
|
||||
"GrafanaManagedAlert.Updated",
|
||||
"GrafanaManagedAlert.UID",
|
||||
"GrafanaManagedAlert.ID",
|
||||
"GrafanaManagedAlert.Data.Model",
|
||||
"GrafanaManagedAlert.NamespaceUID",
|
||||
"GrafanaManagedAlert.NamespaceID",
|
||||
}
|
||||
|
||||
// compare expected and actual and ignore the dynamic fields
|
||||
diff := cmp.Diff(expected, allRules, cmp.FilterPath(func(path cmp.Path) bool {
|
||||
for _, s := range pathsToIgnore {
|
||||
if strings.Contains(path.String(), s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()))
|
||||
|
||||
require.Empty(t, diff)
|
||||
|
||||
for _, rule := range allRules["folder1"][0].Rules {
|
||||
assert.Equal(t, "folder1", rule.GrafanaManagedAlert.NamespaceUID)
|
||||
assert.Equal(t, int64(1), rule.GrafanaManagedAlert.NamespaceID)
|
||||
}
|
||||
for _, rule := range allRules["folder2"][0].Rules {
|
||||
assert.Equal(t, "folder2", rule.GrafanaManagedAlert.NamespaceUID)
|
||||
assert.Equal(t, int64(2), rule.GrafanaManagedAlert.NamespaceID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Get by folder returns groups in folder", func(t *testing.T) {
|
||||
rules, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "folder1")
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
require.Contains(t, rules, "folder1")
|
||||
require.Len(t, rules["folder1"], 1)
|
||||
require.Equal(t, allRules["folder1"], rules["folder1"])
|
||||
})
|
||||
|
||||
t.Run("Get group returns a single group", func(t *testing.T) {
|
||||
rules := apiClient.GetRulesGroup(t, "folder2", allRules["folder2"][0].Name)
|
||||
cmp.Diff(allRules["folder2"][0], rules.GettableRuleGroupConfig)
|
||||
})
|
||||
|
||||
t.Run("Export returns all rules", func(t *testing.T) {
|
||||
var group1File, group2File apimodels.AlertingFileExport
|
||||
getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-export.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup1Raw, &group1File))
|
||||
getGroup2Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-2-export.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup2Raw, &group2File))
|
||||
|
||||
group1File.Groups = append(group1File.Groups, group2File.Groups...)
|
||||
expected := group1File
|
||||
|
||||
pathsToIgnore := []string{
|
||||
"Groups.Rules.UID",
|
||||
"Groups.Folder",
|
||||
}
|
||||
|
||||
// compare expected and actual and ignore the dynamic fields
|
||||
diff := cmp.Diff(expected, allExport, cmp.FilterPath(func(path cmp.Path) bool {
|
||||
for _, s := range pathsToIgnore {
|
||||
if strings.Contains(path.String(), s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()))
|
||||
|
||||
require.Empty(t, diff)
|
||||
|
||||
require.Equal(t, "folder1", allExport.Groups[0].Folder)
|
||||
require.Equal(t, "folder2", allExport.Groups[1].Folder)
|
||||
})
|
||||
|
||||
t.Run("Export from one folder", func(t *testing.T) {
|
||||
expected := allExport.Groups[0]
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"folder1"},
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
|
||||
t.Run("Export from one group", func(t *testing.T) {
|
||||
expected := allExport.Groups[0]
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"folder1"},
|
||||
GroupName: expected.Name,
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
|
||||
t.Run("Export single rule", func(t *testing.T) {
|
||||
expected := allExport.Groups[0]
|
||||
expected.Rules = []apimodels.AlertRuleExport{
|
||||
expected.Rules[0],
|
||||
}
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
RuleUID: expected.Rules[0].UID,
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
t.Log(exportRaw)
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when permissions for folder2 removed", func(t *testing.T) {
|
||||
// remove permissions from folder2
|
||||
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder2")
|
||||
apiClient.ReloadCachedPermissions(t)
|
||||
|
||||
// make sure that folder2 is not included in the response
|
||||
// nolint:gosec
|
||||
resp, err = http.Get(u)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
t.Run("Get all returns all rules", func(t *testing.T) {
|
||||
newAll, status, _ := apiClient.GetAllRulesWithStatus(t)
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
require.NotContains(t, newAll, "folder2")
|
||||
require.Contains(t, newAll, "folder1")
|
||||
})
|
||||
b, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 200)
|
||||
|
||||
body, _ = rulesNamespaceWithoutVariableValues(t, b)
|
||||
expectedGetNamespaceResponseBody = `
|
||||
{
|
||||
"folder1":[
|
||||
{
|
||||
"name":"arulegroup",
|
||||
"interval":"1m",
|
||||
"rules":[
|
||||
{
|
||||
"annotations": {
|
||||
"annotation1": "val1"
|
||||
},
|
||||
"expr":"",
|
||||
"for": "2m",
|
||||
"labels": {
|
||||
"label1": "val1"
|
||||
},
|
||||
"grafana_alert":{
|
||||
"id":1,
|
||||
"orgId":1,
|
||||
"title":"rule under folder folder1",
|
||||
"condition":"A",
|
||||
"data":[
|
||||
{
|
||||
"refId":"A",
|
||||
"queryType":"",
|
||||
"relativeTimeRange":{
|
||||
"from":18000,
|
||||
"to":10800
|
||||
},
|
||||
"datasourceUid":"__expr__",
|
||||
"model":{
|
||||
"expression":"2 + 3 \u003E 1",
|
||||
"intervalMs":1000,
|
||||
"maxDataPoints":43200,
|
||||
"type":"math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated":"2021-02-21T01:10:30Z",
|
||||
"intervalSeconds":60,
|
||||
"is_paused":false,
|
||||
"version":1,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"namespace_id":1,
|
||||
"rule_group":"arulegroup",
|
||||
"no_data_state":"NoData",
|
||||
"exec_err_state":"Alerting"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
|
||||
}
|
||||
|
||||
// Remove permissions from folder1.
|
||||
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1")
|
||||
apiClient.ReloadCachedPermissions(t)
|
||||
{
|
||||
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(u)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
t.Run("Get by folder returns groups in folder", func(t *testing.T) {
|
||||
_, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "folder2")
|
||||
require.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 200)
|
||||
require.JSONEq(t, `{}`, string(b))
|
||||
}
|
||||
t.Run("Get group returns a single group", func(t *testing.T) {
|
||||
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/folder2/arulegroup", apiClient.url)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(u)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("Export returns all rules", func(t *testing.T) {
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, "folder1", export.Groups[0].Folder)
|
||||
})
|
||||
|
||||
t.Run("Export from one folder", func(t *testing.T) {
|
||||
status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"folder2"},
|
||||
})
|
||||
assert.Equal(t, http.StatusUnauthorized, status)
|
||||
})
|
||||
|
||||
t.Run("Export from one group", func(t *testing.T) {
|
||||
status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"folder2"},
|
||||
GroupName: "arulegroup",
|
||||
})
|
||||
assert.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
|
||||
t.Run("Export single rule", func(t *testing.T) {
|
||||
uid := allRules["folder2"][0].Rules[0].GrafanaManagedAlert.UID
|
||||
status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
RuleUID: uid,
|
||||
})
|
||||
require.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
|
||||
t.Run("when all permissions are revoked", func(t *testing.T) {
|
||||
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1")
|
||||
apiClient.ReloadCachedPermissions(t)
|
||||
|
||||
rules, status, _ := apiClient.GetAllRulesWithStatus(t)
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
require.Empty(t, rules)
|
||||
|
||||
status, _ = apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
})
|
||||
require.Equal(t, http.StatusNotFound, status)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func createRule(t *testing.T, client apiClient, folder string) {
|
||||
func createRule(t *testing.T, client apiClient, folder string) apimodels.PostableRuleGroupConfig {
|
||||
t.Helper()
|
||||
|
||||
interval, err := model.ParseDuration("1m")
|
||||
@ -322,6 +362,85 @@ func createRule(t *testing.T, client apiClient, folder string) {
|
||||
status, body := client.PostRulesGroup(t, folder, &rules)
|
||||
assert.Equal(t, http.StatusAccepted, status)
|
||||
require.JSONEq(t, `{"message":"rule group updated successfully"}`, body)
|
||||
return rules
|
||||
}
|
||||
|
||||
func TestAlertRulePostExport(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
// Setup Grafana and its Database
|
||||
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, p)
|
||||
permissionsStore := resourcepermissions.NewStore(store, featuremgmt.WithFeatures())
|
||||
|
||||
// Create a user to make authenticated requests
|
||||
userID := createUser(t, store, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleEditor),
|
||||
Password: "password",
|
||||
Login: "grafana",
|
||||
})
|
||||
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
||||
|
||||
// Create the namespace we'll save our alerts to.
|
||||
apiClient.CreateFolder(t, "folder1", "folder1")
|
||||
|
||||
var group1 apimodels.PostableRuleGroupConfig
|
||||
|
||||
group1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(group1Raw, &group1))
|
||||
|
||||
t.Run("should return in export format", func(t *testing.T) {
|
||||
var expected, actual apimodels.AlertingFileExport
|
||||
getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-export.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup1Raw, &expected))
|
||||
|
||||
status, actualRaw := apiClient.PostRulesExportWithStatus(t, "folder1", &group1, &apimodels.ExportQueryParams{
|
||||
Download: false,
|
||||
Format: "json",
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
require.NoError(t, json.Unmarshal([]byte(actualRaw), &actual))
|
||||
|
||||
pathsToIgnore := []string{
|
||||
"Groups.Rules.UID",
|
||||
"Groups.Folder",
|
||||
"Data.Model", // Model is not amended with default values
|
||||
}
|
||||
|
||||
// compare expected and actual and ignore the dynamic fields
|
||||
diff := cmp.Diff(expected, actual, cmp.FilterPath(func(path cmp.Path) bool {
|
||||
for _, s := range pathsToIgnore {
|
||||
if strings.Contains(path.String(), s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()))
|
||||
|
||||
require.Empty(t, diff)
|
||||
|
||||
require.Equal(t, actual.Groups[0].Folder, "folder1")
|
||||
})
|
||||
|
||||
t.Run("should return 403 when no access to folder", func(t *testing.T) {
|
||||
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1")
|
||||
apiClient.ReloadCachedPermissions(t)
|
||||
|
||||
status, _ := apiClient.PostRulesExportWithStatus(t, "folder1", &group1, &apimodels.ExportQueryParams{
|
||||
Download: false,
|
||||
Format: "json",
|
||||
})
|
||||
require.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationAlertRuleConflictingTitle(t *testing.T) {
|
||||
|
75
pkg/tests/api/alerting/test-data/rulegroup-1-export.json
Normal file
75
pkg/tests/api/alerting/test-data/rulegroup-1-export.json
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"apiVersion": 1,
|
||||
"groups": [
|
||||
{
|
||||
"orgId": 1,
|
||||
"name": "Group1",
|
||||
"folder": "<dynamic>",
|
||||
"interval": "1m",
|
||||
"rules": [
|
||||
{
|
||||
"uid": "<dynamic>",
|
||||
"title": "Rule1",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 \u003e 0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"noDataState": "NoData",
|
||||
"execErrState": "Alerting",
|
||||
"for": "5m",
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"isPaused": false
|
||||
},
|
||||
{
|
||||
"uid": "<dynamic>",
|
||||
"title": "Rule2",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 == 0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"noDataState": "NoData",
|
||||
"execErrState": "Alerting",
|
||||
"for": "5m",
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"isPaused": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
92
pkg/tests/api/alerting/test-data/rulegroup-1-get.json
Normal file
92
pkg/tests/api/alerting/test-data/rulegroup-1-get.json
Normal file
@ -0,0 +1,92 @@
|
||||
{
|
||||
"name": "Group1",
|
||||
"interval": "1m",
|
||||
"rules": [
|
||||
{
|
||||
"expr": "",
|
||||
"for": "5m",
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"id": 1,
|
||||
"orgId": 1,
|
||||
"title": "Rule1",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 > 0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated": "2023-09-29T17:37:19Z",
|
||||
"intervalSeconds": 60,
|
||||
"version": 1,
|
||||
"uid": "<dynamic>",
|
||||
"namespace_uid": "<dynamic>",
|
||||
"namespace_id": 0,
|
||||
"rule_group": "Group1",
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting",
|
||||
"is_paused": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"expr": "",
|
||||
"for": "5m",
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"id": 2,
|
||||
"orgId": 1,
|
||||
"title": "Rule2",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 == 0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated": "2023-09-29T17:37:19Z",
|
||||
"intervalSeconds": 60,
|
||||
"version": 1,
|
||||
"uid": "<dynamic>",
|
||||
"namespace_uid": "<dynamic>",
|
||||
"namespace_id": 0,
|
||||
"rule_group": "Group1",
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting",
|
||||
"is_paused": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
56
pkg/tests/api/alerting/test-data/rulegroup-1-post.json
Normal file
56
pkg/tests/api/alerting/test-data/rulegroup-1-post.json
Normal file
@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "Group1",
|
||||
"interval": "1m",
|
||||
"rules": [
|
||||
{
|
||||
"for" : "5m",
|
||||
"labels" : {
|
||||
"label1" : "test-label"
|
||||
},
|
||||
"annotations" : {
|
||||
"annotation" : "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"title": "Rule1",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 > 0",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting"
|
||||
}
|
||||
},
|
||||
{
|
||||
"for" : "5m",
|
||||
"labels" : {
|
||||
"label1" : "test-label"
|
||||
},
|
||||
"annotations" : {
|
||||
"annotation" : "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"title": "Rule2",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 == 0",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
44
pkg/tests/api/alerting/test-data/rulegroup-2-export.json
Normal file
44
pkg/tests/api/alerting/test-data/rulegroup-2-export.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"apiVersion": 1,
|
||||
"groups": [
|
||||
{
|
||||
"orgId": 1,
|
||||
"name": "Group2",
|
||||
"folder": "<dynamic>",
|
||||
"interval": "1m",
|
||||
"rules": [
|
||||
{
|
||||
"uid": "<dynamic>",
|
||||
"title": "Rule3",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0/0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"noDataState": "NoData",
|
||||
"execErrState": "Error",
|
||||
"for": "5m",
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"isPaused": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
49
pkg/tests/api/alerting/test-data/rulegroup-2-get.json
Normal file
49
pkg/tests/api/alerting/test-data/rulegroup-2-get.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "Group2",
|
||||
"interval": "1m",
|
||||
"rules": [
|
||||
{
|
||||
"expr": "",
|
||||
"for": "5m",
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"id": 3,
|
||||
"orgId": 1,
|
||||
"title": "Rule3",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0/0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated": "2023-09-29T17:37:19Z",
|
||||
"intervalSeconds": 60,
|
||||
"version": 1,
|
||||
"uid": "<dynamic>",
|
||||
"namespace_uid": "<dynamic>",
|
||||
"namespace_id": 0,
|
||||
"rule_group": "Group2",
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Error",
|
||||
"is_paused": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
31
pkg/tests/api/alerting/test-data/rulegroup-2-post.json
Normal file
31
pkg/tests/api/alerting/test-data/rulegroup-2-post.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "Group2",
|
||||
"interval": "1m",
|
||||
"rules": [
|
||||
{
|
||||
"for" : "5m",
|
||||
"labels" : {
|
||||
"label1" : "test-label"
|
||||
},
|
||||
"annotations" : {
|
||||
"annotation" : "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"title": "Rule3",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0/0",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -346,6 +347,42 @@ func (a apiClient) PostRulesGroup(t *testing.T, folder string, group *apimodels.
|
||||
return resp.StatusCode, string(b)
|
||||
}
|
||||
|
||||
func (a apiClient) PostRulesExportWithStatus(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig, params *apimodels.ExportQueryParams) (int, string) {
|
||||
t.Helper()
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err := enc.Encode(group)
|
||||
require.NoError(t, err)
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/export", a.url, folder))
|
||||
require.NoError(t, err)
|
||||
|
||||
if params != nil {
|
||||
q := url.Values{}
|
||||
if params.Format != "" {
|
||||
q.Set("format", params.Format)
|
||||
}
|
||||
if params.Download {
|
||||
q.Set("download", "true")
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, u.String(), &buf)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
require.NoError(t, err)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
return resp.StatusCode, string(b)
|
||||
}
|
||||
|
||||
func (a apiClient) DeleteRulesGroup(t *testing.T, folder string, group string) (int, string) {
|
||||
t.Helper()
|
||||
|
||||
@ -365,6 +402,12 @@ func (a apiClient) DeleteRulesGroup(t *testing.T, folder string, group string) (
|
||||
}
|
||||
|
||||
func (a apiClient) GetRulesGroup(t *testing.T, folder string, group string) apimodels.RuleGroupConfigResponse {
|
||||
result, status, _ := a.GetRulesGroupWithStatus(t, folder, group)
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
return result
|
||||
}
|
||||
|
||||
func (a apiClient) GetRulesGroupWithStatus(t *testing.T, folder string, group string) (apimodels.RuleGroupConfigResponse, int, []byte) {
|
||||
t.Helper()
|
||||
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/%s", a.url, folder, group)
|
||||
// nolint:gosec
|
||||
@ -375,14 +418,16 @@ func (a apiClient) GetRulesGroup(t *testing.T, folder string, group string) apim
|
||||
}()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||
|
||||
result := apimodels.RuleGroupConfigResponse{}
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
return result
|
||||
|
||||
if http.StatusAccepted == resp.StatusCode {
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
}
|
||||
return result, resp.StatusCode, b
|
||||
}
|
||||
|
||||
func (a apiClient) GetAllRulesGroupInFolder(t *testing.T, folder string) apimodels.NamespaceConfigResponse {
|
||||
func (a apiClient) GetAllRulesGroupInFolderWithStatus(t *testing.T, folder string) (apimodels.NamespaceConfigResponse, int, []byte) {
|
||||
t.Helper()
|
||||
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s", a.url, folder)
|
||||
// nolint:gosec
|
||||
@ -393,11 +438,73 @@ func (a apiClient) GetAllRulesGroupInFolder(t *testing.T, folder string) apimode
|
||||
}()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||
|
||||
result := apimodels.NamespaceConfigResponse{}
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
return result
|
||||
if http.StatusAccepted == resp.StatusCode {
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
}
|
||||
return result, resp.StatusCode, b
|
||||
}
|
||||
|
||||
func (a apiClient) GetAllRulesWithStatus(t *testing.T) (apimodels.NamespaceConfigResponse, int, []byte) {
|
||||
t.Helper()
|
||||
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules", a.url)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(u)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
result := apimodels.NamespaceConfigResponse{}
|
||||
if http.StatusOK == resp.StatusCode {
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
}
|
||||
return result, resp.StatusCode, b
|
||||
}
|
||||
|
||||
func (a apiClient) ExportRulesWithStatus(t *testing.T, params *apimodels.AlertRulesExportParameters) (int, string) {
|
||||
t.Helper()
|
||||
u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/export/rules", a.url))
|
||||
require.NoError(t, err)
|
||||
if params != nil {
|
||||
q := url.Values{}
|
||||
if params.Format != "" {
|
||||
q.Set("format", params.Format)
|
||||
}
|
||||
if params.Download {
|
||||
q.Set("download", "true")
|
||||
}
|
||||
if len(params.FolderUID) > 0 {
|
||||
for _, s := range params.FolderUID {
|
||||
q.Add("folderUid", s)
|
||||
}
|
||||
}
|
||||
if params.GroupName != "" {
|
||||
q.Set("group", params.GroupName)
|
||||
}
|
||||
if params.RuleUID != "" {
|
||||
q.Set("ruleUid", params.RuleUID)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
return resp.StatusCode, string(b)
|
||||
}
|
||||
|
||||
func (a apiClient) SubmitRuleForBacktesting(t *testing.T, config apimodels.BacktestConfig) (int, string) {
|
||||
|
Loading…
Reference in New Issue
Block a user