|
|
|
@ -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 != "" { |
|
|
|
|