Skip to content

Commit 4bdad88

Browse files
committed
fix: add support for per check alerts
1 parent 14b3972 commit 4bdad88

File tree

3 files changed

+203
-0
lines changed

3 files changed

+203
-0
lines changed

model/model.go

+8
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ type CheckDeleteResponse struct {
8585
CheckID int64 `json:"checkId"`
8686
}
8787

88+
type CheckAlert struct {
89+
Name string `json:"name"`
90+
Threshold float64 `json:"threshold"`
91+
Period string `json:"period,omitempty"`
92+
Created int64 `json:"created"`
93+
Modified int64 `json:"modified"`
94+
}
95+
8896
func (e *ResponseError) Error() string {
8997
switch {
9098
case e == nil:

smapi.go

+67
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,73 @@ func (h *Client) Delete(ctx context.Context, url string, auth bool) (*http.Respo
611611
return h.do(ctx, url, http.MethodDelete, auth, nil, nil)
612612
}
613613

614+
// Put is a utility method to send a PUT request to the SM API.
615+
//
616+
// The `url` argument specifies the additional URL path of the request (minus
617+
// the base, which is part of the client). `auth` specifies whether or not to
618+
// include authorization headers. `body` specifies the request body, and
619+
// `headers` specifies the request headers.
620+
func (h *Client) Put(ctx context.Context, url string, auth bool, body io.Reader, headers http.Header) (*http.Response, error) {
621+
return h.do(ctx, h.baseURL+url, http.MethodPut, auth, headers, body)
622+
}
623+
624+
// PutJSON is a utility method to send a PUT request to the SM API with a
625+
// body specified by the `req` argument encoded as JSON.
626+
//
627+
// The `url` argument specifies the additional URL path of the request (minus
628+
// the base, which is part of the client). `auth` specifies whether or not to
629+
// include authorization headers.
630+
func (h *Client) PutJSON(ctx context.Context, url string, auth bool, req interface{}) (*http.Response, error) {
631+
var body bytes.Buffer
632+
633+
var headers http.Header
634+
if req != nil {
635+
headers = defaultHeaders()
636+
637+
if err := json.NewEncoder(&body).Encode(&req); err != nil {
638+
return nil, ErrCannotEncodeJSONRequest
639+
}
640+
}
641+
642+
return h.Put(ctx, url, auth, &body, headers)
643+
}
644+
645+
func (h *Client) UpdateCheckAlerts(ctx context.Context, checkID int64, alerts []model.CheckAlert) ([]model.CheckAlert, error) {
646+
if err := h.requireAuthToken(); err != nil {
647+
return nil, err
648+
}
649+
650+
resp, err := h.PutJSON(ctx, fmt.Sprintf("/check/%d/alerts", checkID), true, &alerts)
651+
if err != nil {
652+
return nil, fmt.Errorf("sending check alerts update request: %w", err)
653+
}
654+
655+
var result []model.CheckAlert
656+
if err := ValidateResponse("check alerts update request", resp, &result); err != nil {
657+
return nil, err
658+
}
659+
660+
return result, nil
661+
}
662+
663+
func (h *Client) GetCheckAlerts(ctx context.Context, checkID int64) ([]model.CheckAlert, error) {
664+
if err := h.requireAuthToken(); err != nil {
665+
return nil, err
666+
}
667+
668+
resp, err := h.Get(ctx, fmt.Sprintf("/check/%d/alerts", checkID), true, nil)
669+
if err != nil {
670+
return nil, fmt.Errorf("sending check alerts get request: %w", err)
671+
}
672+
673+
var result []model.CheckAlert
674+
if err := ValidateResponse("check alerts get request", resp, &result); err != nil {
675+
return nil, err
676+
}
677+
678+
return result, nil
679+
}
680+
614681
// HTTPError represents errors returned from the Synthetic Monitoring API
615682
// server.
616683
//

smapi_test.go

+128
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,134 @@ func TestUpdateTenant(t *testing.T) {
14431443
"UpdateTenant mismatch (-want +got)")
14441444
}
14451445

1446+
func TestUpdateCheckAlerts(t *testing.T) {
1447+
orgs := orgs()
1448+
testTenant := orgs.findTenantByOrg(1000)
1449+
testTenantID := testTenant.id
1450+
testCheckID := int64(42)
1451+
1452+
url, mux, cleanup := newTestServer(t)
1453+
defer cleanup()
1454+
mux.Handle("/api/v1/check/42/alerts", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1455+
if err := requireMethod(w, r, http.MethodPut); err != nil {
1456+
return
1457+
}
1458+
1459+
if _, err := requireAuth(orgs, w, r, testTenantID); err != nil {
1460+
return
1461+
}
1462+
1463+
var req []model.CheckAlert
1464+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
1465+
errorResponse(w, http.StatusBadRequest, "cannot decode request")
1466+
return
1467+
}
1468+
1469+
writeResponse(w, http.StatusOK, req)
1470+
}))
1471+
1472+
c := NewClient(url, testTenant.token, http.DefaultClient)
1473+
1474+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
1475+
defer cancel()
1476+
1477+
alerts := []model.CheckAlert{
1478+
{
1479+
Name: "ProbeFailedExecutionsTooHigh",
1480+
Threshold: 95.0,
1481+
Period: "5m",
1482+
Created: 1234567890,
1483+
Modified: 1234567890,
1484+
},
1485+
{
1486+
Name: "TLSTargetCertificateCloseToExpiring",
1487+
Threshold: 7.0, // days until expiration
1488+
Created: 1234567890,
1489+
Modified: 1234567890,
1490+
},
1491+
}
1492+
1493+
result, err := c.UpdateCheckAlerts(ctx, testCheckID, alerts)
1494+
require.NoError(t, err)
1495+
require.NotNil(t, result)
1496+
require.Len(t, result, 2)
1497+
1498+
// Check first alert
1499+
require.Equal(t, "ProbeFailedExecutionsTooHigh", result[0].Name)
1500+
require.Equal(t, 95.0, result[0].Threshold)
1501+
require.Equal(t, "5m", result[0].Period)
1502+
require.Equal(t, int64(1234567890), result[0].Created)
1503+
require.Equal(t, int64(1234567890), result[0].Modified)
1504+
1505+
// Check second alert
1506+
require.Equal(t, "TLSTargetCertificateCloseToExpiring", result[1].Name)
1507+
require.Equal(t, 7.0, result[1].Threshold)
1508+
require.Empty(t, result[1].Period)
1509+
require.Equal(t, int64(1234567890), result[1].Created)
1510+
require.Equal(t, int64(1234567890), result[1].Modified)
1511+
}
1512+
1513+
func TestGetCheckAlerts(t *testing.T) {
1514+
orgs := orgs()
1515+
testTenant := orgs.findTenantByOrg(1000)
1516+
testTenantID := testTenant.id
1517+
testCheckID := int64(42)
1518+
1519+
url, mux, cleanup := newTestServer(t)
1520+
defer cleanup()
1521+
mux.Handle("/api/v1/check/42/alerts", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1522+
if err := requireMethod(w, r, http.MethodGet); err != nil {
1523+
return
1524+
}
1525+
1526+
if _, err := requireAuth(orgs, w, r, testTenantID); err != nil {
1527+
return
1528+
}
1529+
1530+
alerts := []model.CheckAlert{
1531+
{
1532+
Name: "ProbeFailedExecutionsTooHigh",
1533+
Threshold: 95.0,
1534+
Period: "5m",
1535+
Created: 1234567890,
1536+
Modified: 1234567890,
1537+
},
1538+
{
1539+
Name: "TLSTargetCertificateCloseToExpiring",
1540+
Threshold: 7.0, // days until expiration
1541+
Created: 1234567890,
1542+
Modified: 1234567890,
1543+
},
1544+
}
1545+
1546+
writeResponse(w, http.StatusOK, alerts)
1547+
}))
1548+
1549+
c := NewClient(url, testTenant.token, http.DefaultClient)
1550+
1551+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
1552+
defer cancel()
1553+
1554+
result, err := c.GetCheckAlerts(ctx, testCheckID)
1555+
require.NoError(t, err)
1556+
require.NotNil(t, result)
1557+
require.Len(t, result, 2)
1558+
1559+
// Check first alert
1560+
require.Equal(t, "ProbeFailedExecutionsTooHigh", result[0].Name)
1561+
require.Equal(t, 95.0, result[0].Threshold)
1562+
require.Equal(t, "5m", result[0].Period)
1563+
require.Equal(t, int64(1234567890), result[0].Created)
1564+
require.Equal(t, int64(1234567890), result[0].Modified)
1565+
1566+
// Check second alert
1567+
require.Equal(t, "TLSTargetCertificateCloseToExpiring", result[1].Name)
1568+
require.Equal(t, 7.0, result[1].Threshold)
1569+
require.Empty(t, result[1].Period)
1570+
require.Equal(t, int64(1234567890), result[1].Created)
1571+
require.Equal(t, int64(1234567890), result[1].Modified)
1572+
}
1573+
14461574
func newTestServer(t *testing.T) (string, *http.ServeMux, func()) {
14471575
t.Helper()
14481576

0 commit comments

Comments
 (0)