gatus/api/external_endpoint_test.go
raojinlin 59842d5e88
feat(alerting): custom alert support endpoint errors (#844)
* feat(alerting): add support for including endpoint errors in custom alerts

- Updated `buildHTTPRequest` method in `AlertProvider` to accept a `result` parameter.
- Added support for including `[ENDPOINT_ERRORS]` in both the request body and URL, which will be replaced by the errors from `Result.Errors[]`.
- Adjusted `CreateExternalEndpointResult` to capture and store errors from query parameters.
- This allows custom alerts to include detailed error information, enhancing the flexibility of alert notifications.

* feat: add ENDPOINT_ERRORS example

* feat: add tests

* Refactor: code review feedback

* delete unsed errors

* Update README.md

* Apply suggestions from code review

---------

Co-authored-by: raojinlin <raojinlin302@gmail.com>
Co-authored-by: TwiN <twin@linux.com>
2024-09-17 22:26:21 -04:00

162 lines
5.6 KiB
Go

package api
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/TwiN/gatus/v5/alerting"
"github.com/TwiN/gatus/v5/alerting/alert"
"github.com/TwiN/gatus/v5/alerting/provider/discord"
"github.com/TwiN/gatus/v5/config"
"github.com/TwiN/gatus/v5/config/endpoint"
"github.com/TwiN/gatus/v5/config/maintenance"
"github.com/TwiN/gatus/v5/storage/store"
"github.com/TwiN/gatus/v5/storage/store/common/paging"
)
func TestCreateExternalEndpointResult(t *testing.T) {
defer store.Get().Clear()
defer cache.Clear()
cfg := &config.Config{
Alerting: &alerting.Config{
Discord: &discord.AlertProvider{},
},
ExternalEndpoints: []*endpoint.ExternalEndpoint{
{
Name: "n",
Group: "g",
Token: "token",
Alerts: []*alert.Alert{
{
Type: alert.TypeDiscord,
FailureThreshold: 2,
SuccessThreshold: 2,
},
},
},
},
Maintenance: &maintenance.Config{},
}
api := New(cfg)
router := api.Router()
scenarios := []struct {
Name string
Path string
AuthorizationHeaderBearerToken string
ExpectedCode int
}{
{
Name: "no-token",
Path: "/api/v1/endpoints/g_n/external?success=true",
AuthorizationHeaderBearerToken: "",
ExpectedCode: 401,
},
{
Name: "bad-token",
Path: "/api/v1/endpoints/g_n/external?success=true",
AuthorizationHeaderBearerToken: "Bearer bad-token",
ExpectedCode: 401,
},
{
Name: "bad-key",
Path: "/api/v1/endpoints/bad_key/external?success=true",
AuthorizationHeaderBearerToken: "Bearer token",
ExpectedCode: 404,
},
{
Name: "bad-success-value",
Path: "/api/v1/endpoints/g_n/external?success=invalid",
AuthorizationHeaderBearerToken: "Bearer token",
ExpectedCode: 400,
},
{
Name: "good-token-success-true",
Path: "/api/v1/endpoints/g_n/external?success=true",
AuthorizationHeaderBearerToken: "Bearer token",
ExpectedCode: 200,
},
{
Name: "good-token-success-true-with-ignored-error-because-success-true",
Path: "/api/v1/endpoints/g_n/external?success=true&error=failed",
AuthorizationHeaderBearerToken: "Bearer token",
ExpectedCode: 200,
},
{
Name: "good-token-success-false",
Path: "/api/v1/endpoints/g_n/external?success=false",
AuthorizationHeaderBearerToken: "Bearer token",
ExpectedCode: 200,
},
{
Name: "good-token-success-false-again",
Path: "/api/v1/endpoints/g_n/external?success=false",
AuthorizationHeaderBearerToken: "Bearer token",
ExpectedCode: 200,
},
{
Name: "good-token-success-false-with-error",
Path: "/api/v1/endpoints/g_n/external?success=false&error=failed",
AuthorizationHeaderBearerToken: "Bearer token",
ExpectedCode: 200,
},
}
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
request := httptest.NewRequest("POST", scenario.Path, http.NoBody)
if len(scenario.AuthorizationHeaderBearerToken) > 0 {
request.Header.Set("Authorization", scenario.AuthorizationHeaderBearerToken)
}
response, err := router.Test(request)
if err != nil {
return
}
defer response.Body.Close()
if response.StatusCode != scenario.ExpectedCode {
t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode)
}
})
}
t.Run("verify-end-results", func(t *testing.T) {
endpointStatus, err := store.Get().GetEndpointStatus("g", "n", paging.NewEndpointStatusParams().WithResults(1, 10))
if err != nil {
t.Errorf("failed to get endpoint status: %s", err.Error())
return
}
if endpointStatus.Key != "g_n" {
t.Errorf("expected key to be g_n but got %s", endpointStatus.Key)
}
if len(endpointStatus.Results) != 5 {
t.Errorf("expected 3 results but got %d", len(endpointStatus.Results))
}
if !endpointStatus.Results[0].Success {
t.Errorf("expected first result to be successful")
}
if !endpointStatus.Results[1].Success {
t.Errorf("expected second result to be successful")
}
if len(endpointStatus.Results[1].Errors) > 0 {
t.Errorf("expected second result to have no errors")
}
if endpointStatus.Results[2].Success {
t.Errorf("expected third result to be unsuccessful")
}
if endpointStatus.Results[3].Success {
t.Errorf("expected fourth result to be unsuccessful")
}
if endpointStatus.Results[4].Success {
t.Errorf("expected fifth result to be unsuccessful")
}
if len(endpointStatus.Results[4].Errors) == 0 || endpointStatus.Results[4].Errors[0] != "failed" {
t.Errorf("expected fifth result to have errors: failed")
}
externalEndpointFromConfig := cfg.GetExternalEndpointByKey("g_n")
if externalEndpointFromConfig.NumberOfFailuresInARow != 3 {
t.Errorf("expected 3 failures in a row but got %d", externalEndpointFromConfig.NumberOfFailuresInARow)
}
if externalEndpointFromConfig.NumberOfSuccessesInARow != 0 {
t.Errorf("expected 0 successes in a row but got %d", externalEndpointFromConfig.NumberOfSuccessesInARow)
}
})
}