From fbdc66063a2bb34ef455eb791adf7c4e7ea47d9e Mon Sep 17 00:00:00 2001 From: xcezx Date: Fri, 8 May 2020 15:10:26 +0900 Subject: [PATCH 1/2] implement notification-groups APIs --- notification_groups.go | 119 +++++++++++++++ notification_groups_test.go | 292 ++++++++++++++++++++++++++++++++++++ 2 files changed, 411 insertions(+) create mode 100644 notification_groups.go create mode 100644 notification_groups_test.go diff --git a/notification_groups.go b/notification_groups.go new file mode 100644 index 0000000..2356f31 --- /dev/null +++ b/notification_groups.go @@ -0,0 +1,119 @@ +package mackerel + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// NotificationLevel represents a level of notification. +type NotificationLevel string + +// NotificationLevels +const ( + NotificationLevelAll NotificationLevel = "all" + NotificationLevelCritical NotificationLevel = "critical" +) + +// NotificationGroup represents a Mackerel notification group. +// ref. https://mackerel.io/api-docs/entry/notification-groups +type NotificationGroup struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + NotificationLevel NotificationLevel `json:"notificationLevel"` + ChildNotificationGroupIDs []string `json:"childNotificationGroupIds"` + ChildChannelIDs []string `json:"childChannelIds"` + Monitors []*NotificationGroupMonitor `json:"monitors,omitempty"` + Services []*NotificationGroupService `json:"services,omitempty"` +} + +// NotificationGroupMonitor represents a notification target monitor rule. +type NotificationGroupMonitor struct { + ID string `json:"id"` + SkipDefault bool `json:"skipDefault"` +} + +// NotificationGroupService represents a notification target service. +type NotificationGroupService struct { + Name string `json:"name"` +} + +// CreateNotificationGroup creates a notification group. +func (c *Client) CreateNotificationGroup(param *NotificationGroup) (*NotificationGroup, error) { + resp, err := c.PostJSON("/api/v0/notification-groups", param) + defer closeResponse(resp) + if err != nil { + return nil, err + } + + var notificationGroup NotificationGroup + if err := json.NewDecoder(resp.Body).Decode(¬ificationGroup); err != nil { + return nil, err + } + + return ¬ificationGroup, nil +} + +// FindNotificationGroups finds notification groups +func (c *Client) FindNotificationGroups() ([]*NotificationGroup, error) { + req, err := http.NewRequest("GET", c.urlFor("/api/v0/notification-groups").String(), nil) + if err != nil { + return nil, err + } + + resp, err := c.Request(req) + defer closeResponse(resp) + if err != nil { + return nil, err + } + + var data struct { + NotificationGroups []*NotificationGroup `json:"notificationGroups"` + } + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return nil, err + } + + return data.NotificationGroups, nil +} + +// UpdateNotificationGroup updates a notification group +func (c *Client) UpdateNotificationGroup(id string, param *NotificationGroup) (*NotificationGroup, error) { + resp, err := c.PutJSON(fmt.Sprintf("/api/v0/notification-groups/%s", id), param) + defer closeResponse(resp) + if err != nil { + return nil, err + } + + var notificationGroup NotificationGroup + if err := json.NewDecoder(resp.Body).Decode(¬ificationGroup); err != nil { + return nil, err + } + + return ¬ificationGroup, nil +} + +// DeleteNotificationGroup deletes a notification group +func (c *Client) DeleteNotificationGroup(id string) (*NotificationGroup, error) { + req, err := http.NewRequest( + "DELETE", + c.urlFor(fmt.Sprintf("/api/v0/notification-groups/%s", id)).String(), + nil, + ) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + + resp, err := c.Request(req) + defer closeResponse(resp) + if err != nil { + return nil, err + } + + var notificationGroup NotificationGroup + if err := json.NewDecoder(resp.Body).Decode(¬ificationGroup); err != nil { + return nil, err + } + return ¬ificationGroup, nil +} diff --git a/notification_groups_test.go b/notification_groups_test.go new file mode 100644 index 0000000..7de6126 --- /dev/null +++ b/notification_groups_test.go @@ -0,0 +1,292 @@ +package mackerel + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/kylelemons/godebug/pretty" +) + +func TestCreateNotificationGroup(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/api/v0/notification-groups" { + t.Error("request URL should be /api/v0/notification-groups but: ", req.URL.Path) + } + if req.Method != "POST" { + t.Error("request method should be POST but: ", req.Method) + } + + body, _ := ioutil.ReadAll(req.Body) + + var notificationGroup NotificationGroup + if err := json.Unmarshal(body, ¬ificationGroup); err != nil { + t.Fatal("request body should be decoded as json", string(body)) + } + + respJSON, _ := json.Marshal(map[string]interface{}{ + "id": "3JwREyrZGQ9", + "name": notificationGroup.Name, + "notificationLevel": notificationGroup.NotificationLevel, + "childNotificationGroupIds": notificationGroup.ChildNotificationGroupIDs, + "childChannelIds": notificationGroup.ChildChannelIDs, + "monitors": notificationGroup.Monitors, + "services": notificationGroup.Services, + }) + + res.Header()["Content-Type"] = []string{"application/json"} + _, _ = fmt.Fprint(res, string(respJSON)) + })) + defer ts.Close() + + client, _ := NewClientWithOptions("dummy-key", ts.URL, false) + + param := &NotificationGroup{ + Name: "New Notification Group", + NotificationLevel: NotificationLevelAll, + ChildNotificationGroupIDs: []string{"3mUMcLB4ks9", "2w53XJsufQG"}, + ChildChannelIDs: []string{"2nckL8bKy6E", "2w54SREy99h", "3JwPUGFJw2f"}, + Monitors: []*NotificationGroupMonitor{ + {ID: "2CRrhj1SFwG", SkipDefault: true}, + {ID: "3TdoBWxYRQd", SkipDefault: false}, + }, + Services: []*NotificationGroupService{ + {Name: "My Service #01"}, + {Name: "My Service #02"}, + }, + } + + got, err := client.CreateNotificationGroup(param) + if err != nil { + t.Error("err should be nil but: ", err) + } + + want := &NotificationGroup{ + ID: "3JwREyrZGQ9", + Name: param.Name, + NotificationLevel: param.NotificationLevel, + ChildNotificationGroupIDs: param.ChildNotificationGroupIDs, + ChildChannelIDs: param.ChildChannelIDs, + Monitors: param.Monitors, + Services: param.Services, + } + + if diff := pretty.Compare(got, want); diff != "" { + t.Errorf("fail to get correct data: diff (-got +want)\n%s", diff) + } +} + +func TestFindNotificationGroups(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/api/v0/notification-groups" { + t.Error("request URL should be /api/v0/notification-groups but: ", req.URL.Path) + } + if req.Method != "GET" { + t.Error("request method should be GET but: ", req.Method) + } + + respJSON, _ := json.Marshal(map[string][]map[string]interface{}{ + "notificationGroups": { + { + "id": "3Ja3HG3bTwq", + "name": "Default", + "notificationLevel": "all", + "childNotificationGroupIDs": []string{}, + "childChannelIDs": []string{"3Ja3HG3VTaA"}, + }, + { + "id": "3UJaU9eREvw", + "name": "Notification Group #01", + "notificationLevel": "all", + "childNotificationGroupIds": []string{"3Tdq1pz9aLm"}, + "childChannelIds": []string{}, + }, + { + "id": "3Tdq1pz9aLm", + "name": "Notification Group #02", + "notificationLevel": "critical", + "childNotificationGroupIds": []string{}, + "childChannelIds": []string{}, + "monitors": []map[string]interface{}{ + {"id": "3Ja3HG5Mngw", "skipDefault": false}, + }, + "services": []map[string]string{ + {"name": "My Service #01"}, + }, + }, + }, + }) + + res.Header()["Content-Type"] = []string{"application/json"} + _, _ = fmt.Fprint(res, string(respJSON)) + })) + defer ts.Close() + + client, _ := NewClientWithOptions("dummy-key", ts.URL, false) + + got, err := client.FindNotificationGroups() + if err != nil { + t.Error("err should be nil but: ", err) + } + + want := []*NotificationGroup{ + { + ID: "3Ja3HG3bTwq", + Name: "Default", + NotificationLevel: NotificationLevelAll, + ChildNotificationGroupIDs: []string{}, + ChildChannelIDs: []string{"3Ja3HG3VTaA"}, + }, + { + ID: "3UJaU9eREvw", + Name: "Notification Group #01", + NotificationLevel: NotificationLevelAll, + ChildNotificationGroupIDs: []string{"3Tdq1pz9aLm"}, + ChildChannelIDs: []string{}, + }, + { + ID: "3Tdq1pz9aLm", + Name: "Notification Group #02", + NotificationLevel: NotificationLevelCritical, + ChildNotificationGroupIDs: []string{}, + ChildChannelIDs: []string{}, + Monitors: []*NotificationGroupMonitor{ + {ID: "3Ja3HG5Mngw", SkipDefault: false}, + }, + Services: []*NotificationGroupService{ + {Name: "My Service #01"}, + }, + }, + } + + if diff := pretty.Compare(got, want); diff != "" { + t.Errorf("fail to get correct data: diff: (-got +want)\n%s", diff) + } +} + +func TestUpdateNotificationGroup(t *testing.T) { + id := "xxxxxxxxxxx" + ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + if req.URL.Path != fmt.Sprintf("/api/v0/notification-groups/%s", id) { + t.Error("request URL should be /api/v0/notification-groups/ but: ", req.URL.Path) + } + if req.Method != "PUT" { + t.Error("request method should be PUT but: ", req.Method) + } + + body, _ := ioutil.ReadAll(req.Body) + + var notificationGroup NotificationGroup + if err := json.Unmarshal(body, ¬ificationGroup); err != nil { + t.Fatal("request body should be decoded as json", string(body)) + } + + respJSON, _ := json.Marshal(map[string]interface{}{ + "id": id, + "name": notificationGroup.Name, + "notificationLevel": notificationGroup.NotificationLevel, + "childNotificationGroupIds": notificationGroup.ChildNotificationGroupIDs, + "childChannelIds": notificationGroup.ChildChannelIDs, + "monitors": notificationGroup.Monitors, + "services": notificationGroup.Services, + }) + + res.Header()["Content-Type"] = []string{"application/json"} + _, _ = fmt.Fprint(res, string(respJSON)) + })) + defer ts.Close() + + client, _ := NewClientWithOptions("dummy-key", ts.URL, false) + + param := &NotificationGroup{ + Name: "New Notification Group", + NotificationLevel: NotificationLevelCritical, + ChildNotificationGroupIDs: []string{"3mUMcLB4ks9", "2w53XJsufQG"}, + ChildChannelIDs: []string{"2nckL8bKy6E", "2w54SREy99h", "3JwPUGFJw2f"}, + Monitors: []*NotificationGroupMonitor{ + {ID: "2CRrhj1SFwG", SkipDefault: true}, + {ID: "3TdoBWxYRQd", SkipDefault: false}, + }, + Services: []*NotificationGroupService{ + {Name: "My Service #01"}, + {Name: "My Service #02"}, + }, + } + + got, err := client.UpdateNotificationGroup(id, param) + if err != nil { + t.Error("err should be nil but: ", err) + } + + want := &NotificationGroup{ + ID: id, + Name: param.Name, + NotificationLevel: param.NotificationLevel, + ChildNotificationGroupIDs: param.ChildNotificationGroupIDs, + ChildChannelIDs: param.ChildChannelIDs, + Monitors: param.Monitors, + Services: param.Services, + } + + if diff := pretty.Compare(got, want); diff != "" { + t.Errorf("fail to get correct data: diff (-got +want)\n%s", diff) + } +} + +func TestDeleteNotificationGroup(t *testing.T) { + id := "xxxxxxxxxxx" + ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + if req.URL.Path != fmt.Sprintf("/api/v0/notification-groups/%s", id) { + t.Error("request URL should be /api/v0/notification-groups/ but: ", req.URL.Path) + } + if req.Method != "DELETE" { + t.Error("request method should be DELETE but: ", req.Method) + } + + respJSON, _ := json.Marshal(map[string]interface{}{ + "id": id, + "name": "My Notification Group", + "notificationLevel": "all", + "childNotificationGroupIds": []string{}, + "childChannelIds": []string{}, + "monitors": []map[string]interface{}{ + {"id": "2CRrhj1SFwG", "skipDefault": true}, + }, + "services": []map[string]string{ + {"name": "My Service"}, + }, + }) + + res.Header()["Content-Type"] = []string{"application/json"} + _, _ = fmt.Fprint(res, string(respJSON)) + })) + defer ts.Close() + + client, _ := NewClientWithOptions("dummy-key", ts.URL, false) + + got, err := client.DeleteNotificationGroup(id) + if err != nil { + t.Error("err should be nil but: ", err) + } + + want := &NotificationGroup{ + ID: id, + Name: "My Notification Group", + NotificationLevel: NotificationLevelAll, + ChildNotificationGroupIDs: []string{}, + ChildChannelIDs: []string{}, + Monitors: []*NotificationGroupMonitor{ + {ID: "2CRrhj1SFwG", SkipDefault: true}, + }, + Services: []*NotificationGroupService{ + {Name: "My Service"}, + }, + } + + if diff := pretty.Compare(got, want); diff != "" { + t.Errorf("fail to get correct data: diff (-got +want)\n%s", diff) + } +} From fb95d491007d9e6b8d578122c05bb47afd2e6a5c Mon Sep 17 00:00:00 2001 From: xcezx Date: Tue, 12 May 2020 20:43:43 +0900 Subject: [PATCH 2/2] fix test data --- notification_groups_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/notification_groups_test.go b/notification_groups_test.go index 7de6126..b0440b9 100644 --- a/notification_groups_test.go +++ b/notification_groups_test.go @@ -54,8 +54,8 @@ func TestCreateNotificationGroup(t *testing.T) { {ID: "3TdoBWxYRQd", SkipDefault: false}, }, Services: []*NotificationGroupService{ - {Name: "My Service #01"}, - {Name: "My Service #02"}, + {Name: "my-service-01"}, + {Name: "my-service-02"}, }, } @@ -114,7 +114,7 @@ func TestFindNotificationGroups(t *testing.T) { {"id": "3Ja3HG5Mngw", "skipDefault": false}, }, "services": []map[string]string{ - {"name": "My Service #01"}, + {"name": "my-service-01"}, }, }, }, @@ -157,7 +157,7 @@ func TestFindNotificationGroups(t *testing.T) { {ID: "3Ja3HG5Mngw", SkipDefault: false}, }, Services: []*NotificationGroupService{ - {Name: "My Service #01"}, + {Name: "my-service-01"}, }, }, } @@ -211,8 +211,8 @@ func TestUpdateNotificationGroup(t *testing.T) { {ID: "3TdoBWxYRQd", SkipDefault: false}, }, Services: []*NotificationGroupService{ - {Name: "My Service #01"}, - {Name: "My Service #02"}, + {Name: "my-service-01"}, + {Name: "my-service-02"}, }, } @@ -256,7 +256,7 @@ func TestDeleteNotificationGroup(t *testing.T) { {"id": "2CRrhj1SFwG", "skipDefault": true}, }, "services": []map[string]string{ - {"name": "My Service"}, + {"name": "my-service-01"}, }, }) @@ -282,7 +282,7 @@ func TestDeleteNotificationGroup(t *testing.T) { {ID: "2CRrhj1SFwG", SkipDefault: true}, }, Services: []*NotificationGroupService{ - {Name: "My Service"}, + {Name: "my-service-01"}, }, }