diff --git a/alertmanager/alerts_test.go b/alertmanager/alerts_test.go new file mode 100644 index 000000000..fcc503d08 --- /dev/null +++ b/alertmanager/alerts_test.go @@ -0,0 +1,30 @@ +package alertmanager_test + +import ( + "io/ioutil" + "testing" + + log "github.com/Sirupsen/logrus" + "github.com/cloudflare/unsee/alertmanager" + httpmock "gopkg.in/jarcoal/httpmock.v1" +) + +func TestAlertGroupsAPIResponseGet(t *testing.T) { + log.SetOutput(ioutil.Discard) // disable logging to console + httpmock.Activate() + defer httpmock.DeactivateAndReset() + mockJSON, err := ioutil.ReadFile("../mock/api/v1/alerts/groups") + if err != nil { + t.Errorf("Can't open mock 'alerts/groups' file: %s", err.Error()) + } + httpmock.RegisterResponder("GET", "api/v1/alerts/groups", httpmock.NewBytesResponder(200, mockJSON)) + + response := alertmanager.AlertGroupsAPIResponse{} + err = response.Get() + if err != nil { + t.Errorf("AlertGroupsAPIResponse.Get() failed: %s", err.Error()) + } + if response.Status != "success" { + t.Errorf("Invalid AlertGroupsAPIResponse status: %s", response.Status) + } +} diff --git a/alertmanager/remote_test.go b/alertmanager/remote_test.go new file mode 100644 index 000000000..2f5ab3146 --- /dev/null +++ b/alertmanager/remote_test.go @@ -0,0 +1,79 @@ +package alertmanager + +import ( + "io/ioutil" + "testing" + "time" + + log "github.com/Sirupsen/logrus" + httpmock "gopkg.in/jarcoal/httpmock.v1" +) + +type joinURLTest struct { + base string + sub string + url string +} + +var joinURLTests = []joinURLTest{ + joinURLTest{ + base: "http://localhost", + sub: "/sub", + url: "http://localhost/sub", + }, + joinURLTest{ + base: "http://localhost", + sub: "/sub/", + url: "http://localhost/sub", + }, + joinURLTest{ + base: "http://am.example.com", + sub: "/api/v1/alerts", + url: "http://am.example.com/api/v1/alerts", + }, +} + +func TestJoinURL(t *testing.T) { + for _, testCase := range joinURLTests { + url, err := joinURL(testCase.base, testCase.sub) + if err != nil { + t.Errorf("joinURL(%v, %v) failed: %s", testCase.base, testCase.sub, err.Error()) + } + if url != testCase.url { + t.Errorf("Invalid joined url from '%s' + '%s', expected '%s', got '%s'", testCase.base, testCase.sub, testCase.url, url) + } + } +} + +type mockJSONResponse struct { + status string + integer int + yes bool + no bool +} + +func TestGetJSONFromURL(t *testing.T) { + log.SetOutput(ioutil.Discard) // disable logging to console + httpmock.Activate() + defer httpmock.DeactivateAndReset() + mockJSON := `{ + "response": "success", + "integer": 123, + "yes": true, + "no": false + }` + httpmock.RegisterResponder("GET", "http://localhost/", httpmock.NewStringResponder(200, mockJSON)) + + response := mockJSONResponse{} + err := getJSONFromURL("http://localhost/", time.Second, &response) + if err != nil { + t.Errorf("getJSONFromURL() failed: %s", err.Error()) + } + + httpmock.RegisterResponder("GET", "http://localhost/404", httpmock.NewStringResponder(404, "Not found")) + response = mockJSONResponse{} + err = getJSONFromURL("http://localhost/404", time.Second, &response) + if err == nil { + t.Errorf("getJSONFromURL() on invalid url didn't return 404, response: %v", response) + } +} diff --git a/alertmanager/silences_test.go b/alertmanager/silences_test.go new file mode 100644 index 000000000..bbec9c433 --- /dev/null +++ b/alertmanager/silences_test.go @@ -0,0 +1,30 @@ +package alertmanager_test + +import ( + "io/ioutil" + "testing" + + log "github.com/Sirupsen/logrus" + "github.com/cloudflare/unsee/alertmanager" + httpmock "gopkg.in/jarcoal/httpmock.v1" +) + +func TestSilenceAPIResponseGet(t *testing.T) { + log.SetOutput(ioutil.Discard) // disable logging to console + httpmock.Activate() + defer httpmock.DeactivateAndReset() + mockJSON, err := ioutil.ReadFile("../mock/api/v1/silences") + if err != nil { + t.Errorf("Can't open mock 'silences' file: %s", err.Error()) + } + httpmock.RegisterResponder("GET", "api/v1/silences", httpmock.NewBytesResponder(200, mockJSON)) + + response := alertmanager.SilenceAPIResponse{} + err = response.Get() + if err != nil { + t.Errorf("SilenceAPIResponse.Get() failed: %s", err.Error()) + } + if response.Status != "success" { + t.Errorf("Invalid SilenceAPIResponse status: %s", response.Status) + } +} diff --git a/alerts.go b/alerts.go index 6a926f800..3de93afe4 100644 --- a/alerts.go +++ b/alerts.go @@ -2,6 +2,7 @@ package main import ( "strings" + "github.com/cloudflare/unsee/filters" "github.com/cloudflare/unsee/models" ) diff --git a/assets.go b/assets.go index 8d6e49388..574329392 100644 --- a/assets.go +++ b/assets.go @@ -3,12 +3,12 @@ package main import ( "errors" "fmt" + "html/template" + "net/http" "strings" log "github.com/Sirupsen/logrus" assetfs "github.com/elazarl/go-bindata-assetfs" - "html/template" - "net/http" ) type binaryFileSystem struct { diff --git a/mock/api/v1/silences b/mock/api/v1/silences index 4323e6374..dd7e26fcc 100644 --- a/mock/api/v1/silences +++ b/mock/api/v1/silences @@ -14,7 +14,7 @@ "startsAt": "2017-02-18T01:34:34Z", "endsAt": "0001-01-01T00:00:00Z", "createdAt": "2017-02-18T01:34:34Z", - "createdBy": "john@unicorn.corp", + "createdBy": "john@example.com", "comment": "Silenced instance" }, { @@ -34,7 +34,7 @@ "startsAt": "2017-02-18T01:34:34Z", "endsAt": "0001-01-01T00:00:00Z", "createdAt": "2017-02-18T01:34:34Z", - "createdBy": "john@unicorn.corp", + "createdBy": "john@example.com", "comment": "Silenced Host_Down alerts in the dev cluster" } ], diff --git a/transform/color_test.go b/transform/color_test.go new file mode 100644 index 000000000..05f2c209d --- /dev/null +++ b/transform/color_test.go @@ -0,0 +1,87 @@ +package transform_test + +import ( + "testing" + + "github.com/cloudflare/unsee/config" + "github.com/cloudflare/unsee/models" + "github.com/cloudflare/unsee/transform" +) + +type colorTest struct { + config []string + labels map[string]string + colors map[string]string +} + +var colorTests = []colorTest{ + colorTest{ + labels: map[string]string{}, + }, + colorTest{ + labels: map[string]string{ + "node": "localhost", + }, + }, + colorTest{ + config: []string{"node"}, + labels: map[string]string{ + "node": "localhost", + }, + colors: map[string]string{ + "node": "localhost", + }, + }, + colorTest{ + config: []string{"node", "instance"}, + labels: map[string]string{ + "node": "instance", + "env": "instance", + "instance": "server1", + "job": "node_exporter", + }, + colors: map[string]string{ + "node": "instance", + "instance": "server1", + }, + }, + colorTest{ + config: []string{"job", "node", "instance"}, + labels: map[string]string{ + "job": "node_ping", + }, + colors: map[string]string{ + "job": "node_ping", + }, + }, +} + +func TestColorLabel(t *testing.T) { + for _, testCase := range colorTests { + config.Config.ColorLabelsUnique = testCase.config + colorStore := models.UnseeColorMap{} + for key, value := range testCase.labels { + transform.ColorLabel(colorStore, key, value) + } + for key, value := range testCase.colors { + if label, found := colorStore[key]; found { + if _, found := label[value]; !found { + t.Errorf("Expected value '%s' for label '%s' not found in color map", value, key) + } + } else { + t.Errorf("Expected label '%s' not found in color map", key) + } + } + for key, valueMap := range colorStore { + if _, found := testCase.colors[key]; found { + for value, _ := range valueMap { + if value != testCase.colors[key] { + t.Errorf("Unexpected value '%s' for label '%s' found in color map", value, key) + } + } + } else { + t.Errorf("Unexpected label '%s' found in color map", key) + } + } + } +} diff --git a/transform/colors.go b/transform/colors.go index 3e6a41e0a..f2bf0c5c8 100644 --- a/transform/colors.go +++ b/transform/colors.go @@ -2,11 +2,12 @@ package transform import ( "crypto/sha1" - "github.com/cloudflare/unsee/config" - "github.com/cloudflare/unsee/models" "io" "math/rand" + "github.com/cloudflare/unsee/config" + "github.com/cloudflare/unsee/models" + "github.com/hansrodtang/randomcolor" ) diff --git a/transform/jira_test.go b/transform/jira_test.go new file mode 100644 index 000000000..10dff5b48 --- /dev/null +++ b/transform/jira_test.go @@ -0,0 +1,90 @@ +package transform_test + +import ( + "testing" + + "github.com/cloudflare/unsee/models" + "github.com/cloudflare/unsee/transform" +) + +type jiraTest struct { + silence models.AlertmanagerSilence + jiraID string + jiraLink string +} + +var jiraRules = []string{ + "DEVOPS-[0-9]+@https://jira.example.com", + "PROJECT-[0-9]+@https://example.com", +} + +var jiraTests = []jiraTest{ + jiraTest{ + silence: models.AlertmanagerSilence{ + Comment: "Lorem ipsum dolor sit amet", + }, + }, + jiraTest{ + silence: models.AlertmanagerSilence{ + Comment: "DVOPS-123", + }, + }, + jiraTest{ + silence: models.AlertmanagerSilence{ + Comment: "DEVOPS team", + }, + }, + jiraTest{ + silence: models.AlertmanagerSilence{ + Comment: "a project-1 b", + }, + }, + jiraTest{ + silence: models.AlertmanagerSilence{ + Comment: "a PROJECT- b", + }, + }, + jiraTest{ + silence: models.AlertmanagerSilence{ + Comment: "DEVOPS-1", + }, + jiraID: "DEVOPS-1", + jiraLink: "https://jira.example.com/browse/DEVOPS-1", + }, + jiraTest{ + silence: models.AlertmanagerSilence{ + Comment: "DEVOPS-123", + }, + jiraID: "DEVOPS-123", + jiraLink: "https://jira.example.com/browse/DEVOPS-123", + }, + jiraTest{ + silence: models.AlertmanagerSilence{ + Comment: "a DEVOPS-1 b", + }, + jiraID: "DEVOPS-1", + jiraLink: "https://jira.example.com/browse/DEVOPS-1", + }, + jiraTest{ + silence: models.AlertmanagerSilence{ + Comment: "PROJECT-9", + }, + jiraID: "PROJECT-9", + jiraLink: "https://example.com/browse/PROJECT-9", + }, +} + +func TestDetectJIRAs(t *testing.T) { + transform.ParseRules(jiraRules) + for _, testCase := range jiraTests { + jiraID, jiraLink := transform.DetectJIRAs(&testCase.silence) + if jiraID != testCase.jiraID { + t.Errorf("Invalid JIRA ID detected in silence comment '%s', expected '%s', got '%s'", + testCase.silence.Comment, testCase.jiraID, jiraID) + } + if jiraID != testCase.jiraID { + t.Errorf("Invalid JIRA link detected in silence comment '%s', expected '%s', got '%s'", + testCase.silence.Comment, testCase.jiraLink, jiraLink) + } + } +} diff --git a/transform/links_test.go b/transform/links_test.go new file mode 100644 index 000000000..7085345df --- /dev/null +++ b/transform/links_test.go @@ -0,0 +1,102 @@ +package transform_test + +import ( + "reflect" + "testing" + + "github.com/cloudflare/unsee/transform" +) + +type linkTest struct { + before map[string]string + after map[string]string + links map[string]string +} + +var linkTests = []linkTest{ + linkTest{ + before: map[string]string{}, + after: map[string]string{}, + links: map[string]string{}, + }, + linkTest{ + before: map[string]string{ + "key1": "value 1", + "key2": "value2", + "level": "info", + }, + after: map[string]string{ + "key1": "value 1", + "key2": "value2", + "level": "info", + }, + links: map[string]string{}, + }, + linkTest{ + before: map[string]string{ + "key1": "value 1", + "key2": "http://localhost", + "level": "info", + }, + after: map[string]string{ + "key1": "value 1", + "level": "info", + }, + links: map[string]string{ + "key2": "http://localhost", + }, + }, + linkTest{ + before: map[string]string{ + "key1": "value 1", + "key2": "https://example.com/abc", + "level": "info", + }, + after: map[string]string{ + "key1": "value 1", + "level": "info", + }, + links: map[string]string{ + "key2": "https://example.com/abc", + }, + }, + linkTest{ + before: map[string]string{ + "key1": "value 1", + "key2": "file://example/abc", + "level": "info", + }, + after: map[string]string{ + "key1": "value 1", + "key2": "file://example/abc", + "level": "info", + }, + links: map[string]string{}, + }, + linkTest{ + before: map[string]string{ + "key1": "value 1", + "key2": "ftp://example/abc", + "level": "info", + }, + after: map[string]string{ + "key1": "value 1", + "level": "info", + }, + links: map[string]string{ + "key2": "ftp://example/abc", + }, + }, +} + +func TestDetectLinks(t *testing.T) { + for _, testCase := range linkTests { + after, links := transform.DetectLinks(testCase.before) + if !reflect.DeepEqual(after, testCase.after) { + t.Errorf("DetectLinks returned invalid annotation map, expected %v, got %v", testCase.after, after) + } + if !reflect.DeepEqual(links, testCase.links) { + t.Errorf("DetectLinks returned invalid link map, expected %v, got %v", testCase.links, links) + } + } +} diff --git a/transform/slices_test.go b/transform/slices_test.go new file mode 100644 index 000000000..b9a5355f7 --- /dev/null +++ b/transform/slices_test.go @@ -0,0 +1,56 @@ +package transform + +import "testing" + +type sliceTest struct { + array []string + value string + found bool +} + +var sliceTests = []sliceTest{ + sliceTest{ + array: []string{"aa", "bb", "cc", "dd"}, + value: "aa", + found: true, + }, + sliceTest{ + array: []string{"aa", "bb", "cc", "dd"}, + value: "bb", + found: true, + }, + sliceTest{ + array: []string{"aa", "bb", "cc", "dd"}, + value: "cc", + found: true, + }, + sliceTest{ + array: []string{"aa", "bb", "cc", "dd"}, + value: "dd", + found: true, + }, + sliceTest{ + array: []string{"aa", "bb", "cc", "dd"}, + value: "bbcc", + found: false, + }, + sliceTest{ + array: []string{"aa", "bb", "cc", "dd"}, + value: "b", + found: false, + }, + sliceTest{ + array: []string{"aa", "bb", "cc", "dd"}, + value: "", + found: false, + }, +} + +func TestStringInSlice(t *testing.T) { + for _, testCase := range sliceTests { + found := stringInSlice(testCase.array, testCase.value) + if found != testCase.found { + t.Errorf("Check if '%s' in slice %v returned %v, expected %v", testCase.value, testCase.array, found, testCase.found) + } + } +} diff --git a/transform/strip_test.go b/transform/strip_test.go new file mode 100644 index 000000000..fe90f4266 --- /dev/null +++ b/transform/strip_test.go @@ -0,0 +1,71 @@ +package transform_test + +import ( + "reflect" + "testing" + + "github.com/cloudflare/unsee/transform" +) + +type stripTest struct { + strip []string + before map[string]string + after map[string]string +} + +var stripTests = []stripTest{ + stripTest{ + strip: []string{"env"}, + before: map[string]string{ + "host": "localhost", + "env": "production", + "level": "info", + }, + after: map[string]string{ + "host": "localhost", + "level": "info", + }, + }, + stripTest{ + strip: []string{"server"}, + before: map[string]string{ + "host": "localhost", + "env": "production", + "level": "info", + }, + after: map[string]string{ + "host": "localhost", + "env": "production", + "level": "info", + }, + }, + stripTest{ + strip: []string{}, + before: map[string]string{ + "host": "localhost", + "env": "production", + "level": "info", + }, + after: map[string]string{ + "host": "localhost", + "env": "production", + "level": "info", + }, + }, + stripTest{ + strip: []string{"host"}, + before: map[string]string{ + "host": "localhost", + }, + after: map[string]string{}, + }, +} + +func TestStripLables(t *testing.T) { + for _, testCase := range stripTests { + labels := transform.StripLables(testCase.strip, testCase.before) + if !reflect.DeepEqual(labels, testCase.after) { + t.Errorf("StripLables failed, expected %v, got %v", testCase.after, labels) + } + } +}