mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -06:00
Alerting: Add integration tests for mute timings provisioning API (#79810)
This commit is contained in:
parent
14ff9ac679
commit
d160638c67
@ -6,12 +6,18 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/timeinterval"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||
@ -371,6 +377,235 @@ func TestIntegrationProvisioning(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestMuteTimings(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
|
||||
createUser(t, store, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleAdmin),
|
||||
Password: "admin",
|
||||
Login: "admin",
|
||||
})
|
||||
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
|
||||
|
||||
t.Run("default config should return empty list", func(t *testing.T) {
|
||||
mt, status, body := apiClient.GetAllMuteTimingsWithStatus(t)
|
||||
requireStatusCode(t, http.StatusOK, status, body)
|
||||
require.Empty(t, mt)
|
||||
})
|
||||
|
||||
emptyMuteTiming := definitions.MuteTimeInterval{
|
||||
MuteTimeInterval: config.MuteTimeInterval{
|
||||
Name: "Empty Mute Timing",
|
||||
TimeIntervals: []timeinterval.TimeInterval{},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("should create a new mute timing without any intervals", func(t *testing.T) {
|
||||
mt, status, body := apiClient.CreateMuteTimingWithStatus(t, emptyMuteTiming)
|
||||
requireStatusCode(t, http.StatusCreated, status, body)
|
||||
require.Equal(t, emptyMuteTiming.MuteTimeInterval, mt.MuteTimeInterval)
|
||||
require.EqualValues(t, models.ProvenanceAPI, mt.Provenance)
|
||||
})
|
||||
|
||||
anotherMuteTiming := definitions.MuteTimeInterval{
|
||||
MuteTimeInterval: config.MuteTimeInterval{
|
||||
Name: "Not Empty Mute Timing",
|
||||
TimeIntervals: []timeinterval.TimeInterval{
|
||||
{
|
||||
Times: []timeinterval.TimeRange{
|
||||
{
|
||||
StartMinute: 10,
|
||||
EndMinute: 45,
|
||||
},
|
||||
},
|
||||
Weekdays: []timeinterval.WeekdayRange{
|
||||
{
|
||||
InclusiveRange: timeinterval.InclusiveRange{
|
||||
Begin: 0,
|
||||
End: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
InclusiveRange: timeinterval.InclusiveRange{
|
||||
Begin: 4,
|
||||
End: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("should create a new mute timing with some settings", func(t *testing.T) {
|
||||
mt, status, body := apiClient.CreateMuteTimingWithStatus(t, anotherMuteTiming)
|
||||
requireStatusCode(t, http.StatusCreated, status, body)
|
||||
require.Equal(t, anotherMuteTiming.MuteTimeInterval, mt.MuteTimeInterval)
|
||||
require.EqualValues(t, models.ProvenanceAPI, mt.Provenance)
|
||||
})
|
||||
|
||||
t.Run("should return mute timing by name", func(t *testing.T) {
|
||||
mt, status, body := apiClient.GetMuteTimingByNameWithStatus(t, emptyMuteTiming.Name)
|
||||
requireStatusCode(t, http.StatusOK, status, body)
|
||||
require.Equal(t, emptyMuteTiming.MuteTimeInterval, mt.MuteTimeInterval)
|
||||
require.EqualValues(t, "", mt.Provenance) // TODO this is a bug
|
||||
|
||||
mt, status, body = apiClient.GetMuteTimingByNameWithStatus(t, anotherMuteTiming.Name)
|
||||
requireStatusCode(t, http.StatusOK, status, body)
|
||||
require.Equal(t, anotherMuteTiming.MuteTimeInterval, mt.MuteTimeInterval)
|
||||
require.EqualValues(t, "", mt.Provenance) // TODO this is a bug
|
||||
})
|
||||
|
||||
t.Run("should return NotFound if mute timing does not exist", func(t *testing.T) {
|
||||
_, status, body := apiClient.GetMuteTimingByNameWithStatus(t, "some-missing-timing")
|
||||
requireStatusCode(t, http.StatusNotFound, status, body)
|
||||
})
|
||||
|
||||
t.Run("should return all mute timings", func(t *testing.T) {
|
||||
mt, status, body := apiClient.GetAllMuteTimingsWithStatus(t)
|
||||
requireStatusCode(t, http.StatusOK, status, body)
|
||||
require.Len(t, mt, 2)
|
||||
|
||||
slices.SortFunc(mt, func(a, b definitions.MuteTimeInterval) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
require.Equal(t, emptyMuteTiming.MuteTimeInterval, mt[0].MuteTimeInterval)
|
||||
require.EqualValues(t, "", mt[0].Provenance) // TODO this is a bug
|
||||
|
||||
require.Equal(t, anotherMuteTiming.MuteTimeInterval, mt[1].MuteTimeInterval)
|
||||
require.EqualValues(t, "", mt[1].Provenance) // TODO this is a bug
|
||||
})
|
||||
|
||||
t.Run("should get BadRequest if creates a new mute timing with the same name", func(t *testing.T) {
|
||||
m := anotherMuteTiming
|
||||
m.TimeIntervals = nil
|
||||
_, status, body := apiClient.CreateMuteTimingWithStatus(t, m)
|
||||
t.Log(body)
|
||||
requireStatusCode(t, http.StatusBadRequest, status, body)
|
||||
var validationError map[string]any
|
||||
assert.NoError(t, json.Unmarshal([]byte(body), &validationError))
|
||||
assert.Contains(t, validationError, "message")
|
||||
if t.Failed() {
|
||||
t.Fatalf("response: %s", body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should get BadRequest if creates an invalid mute timing", func(t *testing.T) {
|
||||
m := definitions.MuteTimeInterval{
|
||||
MuteTimeInterval: config.MuteTimeInterval{
|
||||
Name: "Invalid",
|
||||
TimeIntervals: []timeinterval.TimeInterval{
|
||||
{
|
||||
Times: []timeinterval.TimeRange{
|
||||
{
|
||||
StartMinute: 20000,
|
||||
EndMinute: 90000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, status, body := apiClient.CreateMuteTimingWithStatus(t, m)
|
||||
t.Log(body)
|
||||
requireStatusCode(t, http.StatusBadRequest, status, body)
|
||||
var validationError map[string]any
|
||||
assert.NoError(t, json.Unmarshal([]byte(body), &validationError))
|
||||
assert.Contains(t, validationError, "message")
|
||||
if t.Failed() {
|
||||
t.Fatalf("response: %s", body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should update existing mute timing", func(t *testing.T) {
|
||||
anotherMuteTiming.TimeIntervals = []timeinterval.TimeInterval{
|
||||
{
|
||||
Times: []timeinterval.TimeRange{
|
||||
{
|
||||
StartMinute: 36,
|
||||
EndMinute: 49,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mt, status, body := apiClient.UpdateMuteTimingWithStatus(t, anotherMuteTiming)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
require.Equal(t, anotherMuteTiming.MuteTimeInterval, mt.MuteTimeInterval)
|
||||
})
|
||||
|
||||
t.Run("should fail to update existing mute timing with invalid one", func(t *testing.T) {
|
||||
mt := anotherMuteTiming
|
||||
mt.TimeIntervals = []timeinterval.TimeInterval{
|
||||
{
|
||||
Times: []timeinterval.TimeRange{
|
||||
{
|
||||
StartMinute: 360000,
|
||||
EndMinute: 490000,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, status, body := apiClient.UpdateMuteTimingWithStatus(t, mt)
|
||||
|
||||
requireStatusCode(t, http.StatusBadRequest, status, body)
|
||||
var validationError map[string]any
|
||||
assert.NoError(t, json.Unmarshal([]byte(body), &validationError))
|
||||
assert.Contains(t, validationError, "message")
|
||||
if t.Failed() {
|
||||
t.Fatalf("response: %s", body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should get NotFound if updates mute timing that does not exist", func(t *testing.T) {
|
||||
mt := definitions.MuteTimeInterval{
|
||||
MuteTimeInterval: config.MuteTimeInterval{
|
||||
Name: "Missing Mute Timing",
|
||||
},
|
||||
}
|
||||
_, status, body := apiClient.UpdateMuteTimingWithStatus(t, mt)
|
||||
requireStatusCode(t, http.StatusNotFound, status, body)
|
||||
})
|
||||
|
||||
t.Run("should delete unused mute timing", func(t *testing.T) {
|
||||
status, body := apiClient.DeleteMuteTimingWithStatus(t, emptyMuteTiming.Name)
|
||||
requireStatusCode(t, http.StatusNoContent, status, body)
|
||||
|
||||
_, status, body = apiClient.GetMuteTimingByNameWithStatus(t, emptyMuteTiming.Name)
|
||||
requireStatusCode(t, http.StatusNotFound, status, body)
|
||||
})
|
||||
|
||||
t.Run("should get BadRequest if deletes used mute-timing", func(t *testing.T) {
|
||||
route, status, response := apiClient.GetRouteWithStatus(t)
|
||||
requireStatusCode(t, http.StatusOK, status, response)
|
||||
route.Routes = append(route.Routes, &definitions.Route{
|
||||
Receiver: route.Receiver,
|
||||
ObjectMatchers: definitions.ObjectMatchers{
|
||||
{
|
||||
Name: "a",
|
||||
Value: "b",
|
||||
},
|
||||
},
|
||||
MuteTimeIntervals: []string{anotherMuteTiming.Name},
|
||||
})
|
||||
status, response = apiClient.UpdateRouteWithStatus(t, route)
|
||||
requireStatusCode(t, http.StatusAccepted, status, response)
|
||||
|
||||
status, response = apiClient.DeleteMuteTimingWithStatus(t, anotherMuteTiming.Name)
|
||||
requireStatusCode(t, http.StatusInternalServerError, status, response) // TODO should be bad request
|
||||
})
|
||||
}
|
||||
|
||||
func createTestRequest(method string, url string, user string, body string) *http.Request {
|
||||
var bodyBuf io.Reader
|
||||
if body != "" {
|
||||
|
@ -594,3 +594,128 @@ func (a apiClient) DeleteDatasource(t *testing.T, uid string) {
|
||||
require.Failf(t, "failed to create data source", "API request to create a datasource failed. Status code: %d, response: %s", resp.StatusCode, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func (a apiClient) GetAllMuteTimingsWithStatus(t *testing.T) (apimodels.MuteTimings, int, string) {
|
||||
t.Helper()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/mute-timings", a.url), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return sendRequest[apimodels.MuteTimings](t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func (a apiClient) GetMuteTimingByNameWithStatus(t *testing.T, name string) (apimodels.MuteTimeInterval, int, string) {
|
||||
t.Helper()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, name), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return sendRequest[apimodels.MuteTimeInterval](t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func (a apiClient) CreateMuteTimingWithStatus(t *testing.T, interval apimodels.MuteTimeInterval) (apimodels.MuteTimeInterval, int, string) {
|
||||
t.Helper()
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err := enc.Encode(interval)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/provisioning/mute-timings", a.url), &buf)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
require.NoError(t, err)
|
||||
|
||||
return sendRequest[apimodels.MuteTimeInterval](t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
func (a apiClient) UpdateMuteTimingWithStatus(t *testing.T, interval apimodels.MuteTimeInterval) (apimodels.MuteTimeInterval, int, string) {
|
||||
t.Helper()
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err := enc.Encode(interval)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, interval.Name), &buf)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
require.NoError(t, err)
|
||||
|
||||
return sendRequest[apimodels.MuteTimeInterval](t, req, http.StatusAccepted)
|
||||
}
|
||||
|
||||
func (a apiClient) DeleteMuteTimingWithStatus(t *testing.T, name string) (int, string) {
|
||||
t.Helper()
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, name), nil)
|
||||
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()
|
||||
}()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
return resp.StatusCode, string(body)
|
||||
}
|
||||
|
||||
func (a apiClient) GetRouteWithStatus(t *testing.T) (apimodels.Route, int, string) {
|
||||
t.Helper()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/policies", a.url), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return sendRequest[apimodels.Route](t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func (a apiClient) UpdateRouteWithStatus(t *testing.T, route apimodels.Route) (int, string) {
|
||||
t.Helper()
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err := enc.Encode(route)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/policies", a.url), &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()
|
||||
}()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
return resp.StatusCode, string(body)
|
||||
}
|
||||
|
||||
func sendRequest[T any](t *testing.T, req *http.Request, successStatusCode int) (T, int, string) {
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result T
|
||||
|
||||
if resp.StatusCode != successStatusCode {
|
||||
return result, resp.StatusCode, string(body)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &result)
|
||||
require.NoError(t, err)
|
||||
return result, resp.StatusCode, string(body)
|
||||
}
|
||||
|
||||
func requireStatusCode(t *testing.T, expected, actual int, response string) {
|
||||
require.Equalf(t, expected, actual, "Unexpected status. Response: %s", response)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user