b4a48fd928
* Adds development instructions to README * Replaces deprecated io/ioutil with io package * Catch all thrown errors and handle them. - not catching errors could result in unknown behaviour * Fix gofmt formatting issues * Refactor to allow http client to be provided - default client does not have timeout etc., we may instead wish to provide a custom http client. * Refactor to something closer to the standard go layout - separates alert forwarder into separate package to allow for testing/reuse * Split out types, and split Discord client into its own package * Renaming of symbols for readability - no need to abbreviate words in modern IDEs * remove go-vet hook as it is broken when go files are not in root directory * unit tests for ~90% coverage * Update picture in README * Return error status codes to caller in event of error from Discord * Remove panic, replace with error status code response and log message. Improve the status codes that are returned to provide more context on what has occurred. * Graceful shutdown of server, including signal handling * Integration tests - mocks Discord server - tests Happy case and a couple of unhappy cases - most edge conditions are otherwise tested in unit tests * CheckWebHook should return errors instead of logging - additional checks in tests for nil objects - attempt to solve integration test pollution by using different port numbers to prevent potential collision - Temporarily comment out test causing interaction pollution with other tests * structured logging * feat(exponential backoff): Added to Discord client * Serve prometheus metrics - Monitoring for the discord client * adds correlation ID to logging * refactors the mock http client to allow it to work with instrumentation for monitoring * Helm chart service monitor * Improved flag and env var parsing * Application version passed in via build args * Order of precedence of configuration configuration file<environment variable<command line * Mounts secret to file instead of in environment variable * Adds build tag to integration tests to prevent them being run as a unit test
128 lines
4.4 KiB
Go
128 lines
4.4 KiB
Go
//go:build !unit
|
|
// +build !unit
|
|
|
|
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/specklesystems/alertmanager-discord/pkg/alertmanager"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const (
|
|
serverListenAddress = "127.0.0.1:9096"
|
|
)
|
|
|
|
func Test_Serve_HappyPath(t *testing.T) {
|
|
// create a mock discord server to respond to our request
|
|
receivedRequest := make(chan bool, 1)
|
|
mockDiscordServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Discord mock server will always return with Status Code 200 OK
|
|
receivedRequest <- true // notify the channel that the discord server received the request
|
|
}))
|
|
defer mockDiscordServer.Close()
|
|
|
|
amds := AlertManagerDiscordServer{}
|
|
defer func() {
|
|
err := amds.Shutdown()
|
|
assert.NoError(t, err, "server shutdown should not error")
|
|
}()
|
|
|
|
_, err := amds.ListenAndServe(mockDiscordServer.URL, serverListenAddress)
|
|
assert.NoError(t, err, "server ListenAndServe should not error")
|
|
|
|
client := http.Client{
|
|
Timeout: 500 * time.Millisecond,
|
|
}
|
|
|
|
res, err := client.Get(fmt.Sprintf("http://%s/liveness", serverListenAddress))
|
|
assert.NotNil(t, res, "response to GET '/liveness' should not be nil")
|
|
assert.Equal(t, http.StatusOK, res.StatusCode, "GET liveness should return status code OK (200)")
|
|
res, err = client.Get(fmt.Sprintf("http://%s/readiness", serverListenAddress))
|
|
assert.NotNil(t, res, "response to GET '/readiness' should not be nil")
|
|
assert.Equal(t, http.StatusOK, res.StatusCode, "GET readiness should return status code OK (200)")
|
|
res, err = client.Get(fmt.Sprintf("http://%s/favicon.ico", serverListenAddress))
|
|
assert.NotNil(t, res, "response to GET '/favicon.ico' should not be nil")
|
|
assert.Equal(t, http.StatusOK, res.StatusCode, "GET favicon.ico should return status code OK (200)")
|
|
res, err = client.Get(fmt.Sprintf("http://%s/metrics", serverListenAddress))
|
|
assert.NotNil(t, res, "response to GET '/metrics' should not be nil")
|
|
assert.Equal(t, http.StatusOK, res.StatusCode, "GET favicon.ico should return status code OK (200)")
|
|
|
|
// assert mock discord server received expected json
|
|
ao := alertmanager.Out{
|
|
Alerts: []alertmanager.Alert{
|
|
{
|
|
Status: alertmanager.StatusFiring,
|
|
},
|
|
},
|
|
CommonAnnotations: struct {
|
|
Summary string `json:"summary"`
|
|
}{
|
|
Summary: "a_common_annotation_summary",
|
|
},
|
|
}
|
|
|
|
aoJson, err := json.Marshal(ao)
|
|
assert.NoError(t, err, "marshalling alertmanager out")
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/", serverListenAddress), bytes.NewReader(aoJson))
|
|
assert.NoError(t, err, "creating http request")
|
|
req.Host = mockDiscordServer.URL
|
|
|
|
res, err = client.Do(req)
|
|
defer func() {
|
|
if res != nil && res.Body != nil {
|
|
res.Body.Close()
|
|
}
|
|
}()
|
|
assert.NoError(t, err, "sending request to alertmanager-discord server.")
|
|
assert.NotNil(t, res, "response to POST '/' should not be nil")
|
|
assert.Equal(t, http.StatusOK, res.StatusCode, "sending valid alertmanager data should expect http response status code")
|
|
assert.True(t, <-receivedRequest, "Mock discord server should have received response") // will wait until the request is received
|
|
|
|
// TODO assert log lines were generated
|
|
|
|
// TODO assert prometheus metrics were generated
|
|
}
|
|
|
|
// Test with invalid URL, throws an error
|
|
func Test_Server_InvalidDiscordUrl(t *testing.T) {
|
|
amds := AlertManagerDiscordServer{}
|
|
defer func() {
|
|
err := amds.Shutdown()
|
|
assert.NoError(t, err, "server shutdown should not error")
|
|
}()
|
|
|
|
_, err := amds.ListenAndServe("https://example.org/not/a/discord/webhook/api", "127.0.0.1:9095")
|
|
assert.Error(t, err, "server ListenAndServe should return an error for an invalid url")
|
|
}
|
|
|
|
// // Commented out as some interaction with the Server_HappyPath test causes that to fail ~5% of runs
|
|
// func Test_Server_With_EmptyListenAddress_DefaultsToListenAddress(t *testing.T) {
|
|
// amds := AlertManagerDiscordServer{}
|
|
// defer func() {
|
|
// err := amds.Shutdown()
|
|
// NoError(t, err, "server shutdown should not error")
|
|
// }()
|
|
|
|
// _, err := amds.ListenAndServe("http://localhost/", "")
|
|
// NoError(t, err, "server ListenAndServe should not error")
|
|
|
|
// client := http.Client{
|
|
// Timeout: 500 * time.Millisecond,
|
|
// }
|
|
|
|
// // it should have defaulted to the default listen address
|
|
// res, err := client.Get(fmt.Sprintf("http://%s/liveness", "127.0.0.1:9094"))
|
|
// NotNil(t, res, "Response should not be nil")
|
|
// EqualInt(t, http.StatusOK, res.StatusCode, "Liveness probe should return status code OK (200)")
|
|
// }
|