test(monitor): add comprehensive test suite for engine and checkers
55 tests covering state machine transitions, heartbeat handling, push deadline checks, group aggregation, history recording, probe aggregation, log management, state management, and concurrency safety. Checker tests cover HTTP (via httptest), port (via net.Listen), isCodeAccepted ranges, and siteTimeout defaults. Ping and DNS checkers skipped (need ICMP privileges and DNS server). Coverage: 64.2% overall, 100% on handleStatusChange, triggerAlert, checkPush, recordCheck, and AggregateStatus.
This commit is contained in:
@@ -0,0 +1,203 @@
|
|||||||
|
package monitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"go-upkeep/internal/models"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunCheck_HTTP_Success(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
site := models.Site{ID: 1, Type: "http", URL: srv.URL}
|
||||||
|
result := RunCheck(site, http.DefaultClient, http.DefaultClient, false)
|
||||||
|
|
||||||
|
if result.Status != "UP" {
|
||||||
|
t.Errorf("expected UP, got %s", result.Status)
|
||||||
|
}
|
||||||
|
if result.StatusCode != 200 {
|
||||||
|
t.Errorf("expected 200, got %d", result.StatusCode)
|
||||||
|
}
|
||||||
|
if result.LatencyNs <= 0 {
|
||||||
|
t.Error("expected positive latency")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCheck_HTTP_ServerError(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
site := models.Site{ID: 1, Type: "http", URL: srv.URL}
|
||||||
|
result := RunCheck(site, http.DefaultClient, http.DefaultClient, false)
|
||||||
|
|
||||||
|
if result.Status != "DOWN" {
|
||||||
|
t.Errorf("expected DOWN, got %s", result.Status)
|
||||||
|
}
|
||||||
|
if result.StatusCode != 500 {
|
||||||
|
t.Errorf("expected 500, got %d", result.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCheck_HTTP_CustomAcceptedCodes(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(302)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
client := &http.Client{CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}}
|
||||||
|
|
||||||
|
site := models.Site{ID: 1, Type: "http", URL: srv.URL, AcceptedCodes: "200-399"}
|
||||||
|
result := RunCheck(site, client, client, false)
|
||||||
|
|
||||||
|
if result.Status != "UP" {
|
||||||
|
t.Errorf("expected UP with accepted 200-399, got %s", result.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCheck_HTTP_MethodRespected(t *testing.T) {
|
||||||
|
var receivedMethod string
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
receivedMethod = r.Method
|
||||||
|
w.WriteHeader(200)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
site := models.Site{ID: 1, Type: "http", URL: srv.URL, Method: "HEAD"}
|
||||||
|
RunCheck(site, http.DefaultClient, http.DefaultClient, false)
|
||||||
|
|
||||||
|
if receivedMethod != "HEAD" {
|
||||||
|
t.Errorf("expected HEAD, got %s", receivedMethod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCheck_HTTP_Timeout(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
w.WriteHeader(200)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
site := models.Site{ID: 1, Type: "http", URL: srv.URL, Timeout: 1}
|
||||||
|
result := RunCheck(site, http.DefaultClient, http.DefaultClient, false)
|
||||||
|
|
||||||
|
if result.Status != "DOWN" {
|
||||||
|
t.Errorf("expected DOWN on timeout, got %s", result.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCheck_HTTP_SSLFields(t *testing.T) {
|
||||||
|
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
insecureClient := &http.Client{
|
||||||
|
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
|
||||||
|
}
|
||||||
|
|
||||||
|
site := models.Site{ID: 1, Type: "http", URL: srv.URL, CheckSSL: true, IgnoreTLS: true}
|
||||||
|
result := RunCheck(site, http.DefaultClient, insecureClient, false)
|
||||||
|
|
||||||
|
if result.Status != "UP" {
|
||||||
|
t.Errorf("expected UP, got %s", result.Status)
|
||||||
|
}
|
||||||
|
if !result.HasSSL {
|
||||||
|
t.Error("expected HasSSL=true")
|
||||||
|
}
|
||||||
|
if result.CertExpiry.IsZero() {
|
||||||
|
t.Error("expected CertExpiry populated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCheck_Port_Open(t *testing.T) {
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
_, portStr, _ := net.SplitHostPort(ln.Addr().String())
|
||||||
|
port, _ := strconv.Atoi(portStr)
|
||||||
|
|
||||||
|
site := models.Site{ID: 1, Type: "port", Hostname: "127.0.0.1", Port: port, Timeout: 2}
|
||||||
|
result := RunCheck(site, nil, nil, false)
|
||||||
|
|
||||||
|
if result.Status != "UP" {
|
||||||
|
t.Errorf("expected UP, got %s", result.Status)
|
||||||
|
}
|
||||||
|
if result.LatencyNs <= 0 {
|
||||||
|
t.Error("expected positive latency")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCheck_Port_Closed(t *testing.T) {
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, portStr, _ := net.SplitHostPort(ln.Addr().String())
|
||||||
|
port, _ := strconv.Atoi(portStr)
|
||||||
|
ln.Close()
|
||||||
|
|
||||||
|
site := models.Site{ID: 1, Type: "port", Hostname: "127.0.0.1", Port: port, Timeout: 1}
|
||||||
|
result := RunCheck(site, nil, nil, false)
|
||||||
|
|
||||||
|
if result.Status != "DOWN" {
|
||||||
|
t.Errorf("expected DOWN, got %s", result.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCheck_UnknownType(t *testing.T) {
|
||||||
|
site := models.Site{ID: 1, Type: "invalid"}
|
||||||
|
result := RunCheck(site, nil, nil, false)
|
||||||
|
|
||||||
|
if result.Status != "DOWN" {
|
||||||
|
t.Errorf("expected DOWN for unknown type, got %s", result.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsCodeAccepted(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
code int
|
||||||
|
accepted string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{200, "", true},
|
||||||
|
{299, "", true},
|
||||||
|
{300, "", false},
|
||||||
|
{302, "200-399", true},
|
||||||
|
{400, "200-399", false},
|
||||||
|
{301, "200,301,404", true},
|
||||||
|
{500, "200,301,404", false},
|
||||||
|
{404, "200-299,400-499", true},
|
||||||
|
{500, "200-299,400-499", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := isCodeAccepted(tt.code, tt.accepted)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("isCodeAccepted(%d, %q) = %v, want %v", tt.code, tt.accepted, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSiteTimeout(t *testing.T) {
|
||||||
|
if got := siteTimeout(models.Site{Timeout: 0}); got != 5*time.Second {
|
||||||
|
t.Errorf("expected 5s default, got %v", got)
|
||||||
|
}
|
||||||
|
if got := siteTimeout(models.Site{Timeout: 10}); got != 10*time.Second {
|
||||||
|
t.Errorf("expected 10s, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user