mirror of
https://github.com/airlabspl/uptimemonitor.git
synced 2025-08-14 20:29:16 +02:00
initial commit
This commit is contained in:
201
test/check_test.go
Normal file
201
test/check_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/service"
|
||||
)
|
||||
|
||||
func TestCheck_ListChecks(t *testing.T) {
|
||||
t.Run("setup is required to load checks", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/monitors/1/checks").
|
||||
AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
})
|
||||
|
||||
t.Run("guests cannot load checks", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Get("/monitors/1/checks").
|
||||
AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
|
||||
t.Run("monitor has to exist", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Get("/monitors/1/checks").
|
||||
AssertStatusCode(http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("latest checks are returned", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
monitor, _ := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "https://example.com",
|
||||
})
|
||||
|
||||
tc.Store.CreateCheck(t.Context(), uptimemonitor.Check{
|
||||
MonitorID: monitor.ID,
|
||||
Monitor: monitor,
|
||||
})
|
||||
|
||||
tc.LogIn().
|
||||
Get("/monitors/1/checks").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`div[id="monitors-1-checks-1"]`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheck_PeriodicChecks(t *testing.T) {
|
||||
t.Run("it does not create checks if no monitors are defined", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
service := service.New(tc.Store)
|
||||
ch := service.StartCheck()
|
||||
|
||||
service.RunCheck(t.Context(), ch)
|
||||
tc.AssertDatabaseCount("checks", 0)
|
||||
|
||||
service.RunCheck(t.Context(), ch)
|
||||
tc.AssertDatabaseCount("checks", 0)
|
||||
})
|
||||
|
||||
t.Run("it creates checks every minute", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "https://example.com",
|
||||
})
|
||||
|
||||
service := service.CheckService{
|
||||
Store: tc.Store,
|
||||
}
|
||||
ch := service.StartCheck()
|
||||
|
||||
service.RunCheck(t.Context(), ch)
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
// tc.AssertDatabaseCount("checks", 2) // todo: fix
|
||||
})
|
||||
|
||||
t.Run("checks can use different http methods", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: tc.Server.URL + "/test/post", HttpMethod: http.MethodPost})
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: tc.Server.URL + "/test/patch", HttpMethod: http.MethodPatch})
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: tc.Server.URL + "/test/put", HttpMethod: http.MethodPut})
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: tc.Server.URL + "/test/delete", HttpMethod: http.MethodDelete})
|
||||
|
||||
service := service.CheckService{Store: tc.Store}
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
tc.AssertDatabaseCount("checks", 4)
|
||||
tc.AssertDatabaseCount("incidents", 0)
|
||||
|
||||
first, err := tc.Store.GetCheckByID(t.Context(), 1)
|
||||
tc.AssertNoError(err)
|
||||
|
||||
second, err := tc.Store.GetCheckByID(t.Context(), 2)
|
||||
tc.AssertNoError(err)
|
||||
|
||||
third, err := tc.Store.GetCheckByID(t.Context(), 3)
|
||||
tc.AssertNoError(err)
|
||||
|
||||
fifth, err := tc.Store.GetCheckByID(t.Context(), 4)
|
||||
tc.AssertNoError(err)
|
||||
|
||||
tc.AssertEqual(http.StatusOK, first.StatusCode)
|
||||
tc.AssertEqual(http.StatusOK, second.StatusCode)
|
||||
tc.AssertEqual(http.StatusOK, third.StatusCode)
|
||||
tc.AssertEqual(http.StatusOK, fifth.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("checks can send custom body", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: tc.Server.URL + "/test/body", HttpMethod: http.MethodPost, HttpBody: `{"test":123}`})
|
||||
|
||||
service := service.CheckService{Store: tc.Store}
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
check, _ := tc.Store.GetCheckByID(t.Context(), 1)
|
||||
tc.AssertEqual(http.StatusOK, check.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("checks can send custom headers", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: tc.Server.URL + "/test/headers", HttpMethod: http.MethodPost, HttpHeaders: `{"test":"abc"}`})
|
||||
|
||||
service := service.CheckService{Store: tc.Store}
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
check, _ := tc.Store.GetCheckByID(t.Context(), 1)
|
||||
tc.AssertEqual(http.StatusOK, check.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheck_Cleanup(t *testing.T) {
|
||||
t.Run("old cleanups are removed", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: tc.Server.URL + "/test/200", HttpMethod: http.MethodGet})
|
||||
|
||||
tc.Store.CreateCheck(t.Context(), uptimemonitor.Check{
|
||||
MonitorID: 1,
|
||||
CreatedAt: time.Now().Add(-time.Hour).Add(-15 * time.Minute),
|
||||
})
|
||||
|
||||
tc.Store.CreateCheck(t.Context(), uptimemonitor.Check{
|
||||
MonitorID: 1,
|
||||
})
|
||||
|
||||
tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 1,
|
||||
CreatedAt: time.Now().Add(-time.Hour * 24 * 8),
|
||||
})
|
||||
|
||||
tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 1,
|
||||
})
|
||||
|
||||
tc.AssertDatabaseCount("checks", 2)
|
||||
tc.AssertDatabaseCount("incidents", 2)
|
||||
|
||||
service := service.CheckService{Store: tc.Store}
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
tc.AssertDatabaseCount("checks", 2) // one new, old one deleted
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
|
||||
service.RunCheck(t.Context(), ch)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
tc.AssertDatabaseCount("checks", 3)
|
||||
})
|
||||
}
|
62
test/home_test.go
Normal file
62
test/home_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"uptimemonitor"
|
||||
)
|
||||
|
||||
func TestHome(t *testing.T) {
|
||||
t.Run("setup is required to access home page", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/").AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
})
|
||||
|
||||
t.Run("guests cannot access home page", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Get("/").AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
|
||||
t.Run("monitors are displayed on home page", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "https://example.com",
|
||||
})
|
||||
|
||||
tc.LogIn().
|
||||
Get("/").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`[hx-get="/monitors"]`)
|
||||
})
|
||||
|
||||
t.Run("incidents are displayed on home page", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "https://example.com",
|
||||
})
|
||||
|
||||
tc.LogIn().
|
||||
Get("/").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`[hx-get="/incidents"]`)
|
||||
})
|
||||
|
||||
t.Run("if there are no monitors, user is redirected to the new page", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Get("/").
|
||||
AssertRedirect(http.StatusSeeOther, "/new")
|
||||
})
|
||||
}
|
357
test/incident_test.go
Normal file
357
test/incident_test.go
Normal file
@@ -0,0 +1,357 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/service"
|
||||
)
|
||||
|
||||
func TestIncident(t *testing.T) {
|
||||
t.Run("no incident is created when check succeeds", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
service := service.New(tc.Store)
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: tc.Server.URL + "/test/200",
|
||||
})
|
||||
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
tc.AssertDatabaseCount("incidents", 0)
|
||||
tc.AssertDatabaseCount("checks", 1)
|
||||
})
|
||||
|
||||
t.Run("incident is created when check fails", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
service := service.New(tc.Store)
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: tc.Server.URL + "/test/404",
|
||||
})
|
||||
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
tc.AssertDatabaseCount("checks", 1)
|
||||
})
|
||||
|
||||
t.Run("new incident is not created for the same monitor if it already exists", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
service := service.New(tc.Store)
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: tc.Server.URL + "/test/500",
|
||||
})
|
||||
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
tc.AssertDatabaseCount("checks", 1)
|
||||
|
||||
ch = service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
})
|
||||
|
||||
t.Run("incident is created when check fails with different status code", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
service := service.New(tc.Store)
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: tc.Server.URL + "/test/500",
|
||||
})
|
||||
|
||||
tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 1,
|
||||
StatusCode: 404,
|
||||
ResponseTimeMs: 100,
|
||||
Body: "not found",
|
||||
Headers: "Content-Type: text/plain",
|
||||
})
|
||||
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
tc.AssertDatabaseCount("incidents", 2)
|
||||
})
|
||||
|
||||
t.Run("invalid domains create incidents", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
service := service.New(tc.Store)
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://invalid-url",
|
||||
})
|
||||
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
})
|
||||
|
||||
t.Run("incidents get resolved", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
service := service.New(tc.Store)
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: tc.Server.URL + "/test/even",
|
||||
})
|
||||
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
|
||||
service.RunCheck(t.Context(), ch)
|
||||
time.Sleep(time.Second)
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
|
||||
incident, _ := tc.Store.LastOpenIncident(t.Context(), 1)
|
||||
if incident.ID != 0 {
|
||||
t.Fatalf("expected not to found any incidents,found: %v", incident)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIncident_ListIncidents(t *testing.T) {
|
||||
t.Run("setup is required", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/incidents").AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
})
|
||||
|
||||
t.Run("guests cannot list incidents", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Get("/incidents").AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
|
||||
t.Run("users can list incidents", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 1,
|
||||
StatusCode: 404,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 2,
|
||||
StatusCode: 500,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
tc.LogIn().Get("/incidents").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`[id="incidents-1"]`).
|
||||
AssertElementVisible(`[id="incidents-2"]`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIncident_ListMonitorIncidents(t *testing.T) {
|
||||
t.Run("setup is required", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/monitors/1/incidents").AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
})
|
||||
|
||||
t.Run("guests cannot list incidents", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Get("/monitors/1/incidents").AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
|
||||
t.Run("users can list incidents", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 1,
|
||||
StatusCode: 404,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
tc.LogIn().Get("/monitors/1/incidents").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`[id="incidents-1"]`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIncident_RemoveIncidents(t *testing.T) {
|
||||
t.Run("setup is required", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Delete("/incidents/1").AssertStatusCode(http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("guests cannot remove incidents", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
tc.Delete("/incidents/1").AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
|
||||
t.Run("remove incident form is visible", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
m, _ := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
i, _ := tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 1,
|
||||
StatusCode: 404,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
tc.LogIn().
|
||||
Get(fmt.Sprintf("/m/%s/i/%s", m.Uuid, i.Uuid)).
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`form[hx-delete="/incidents/1"]`)
|
||||
})
|
||||
|
||||
t.Run("users can remove incidents", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
m, _ := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 1,
|
||||
StatusCode: 404,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
tc.LogIn().
|
||||
Delete("/incidents/1").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertHeader("HX-Redirect", fmt.Sprintf("/m/%s", m.Uuid))
|
||||
|
||||
tc.AssertDatabaseCount("incidents", 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIncident_IncidentPage(t *testing.T) {
|
||||
t.Run("setup is required", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/m/uuid/i/uuid").AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
})
|
||||
|
||||
t.Run("guests cannot view incidents", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Get("/m/uuid/i/uuid").AssertRedirect(http.StatusSeeOther, "/login")
|
||||
|
||||
})
|
||||
|
||||
t.Run("incident has to exist", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
m, _ := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.LogIn().Get(fmt.Sprintf("/m/%s/i/uuid", m.Uuid)).
|
||||
AssertStatusCode(http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("monitor has to exist", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
i, _ := tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 1,
|
||||
StatusCode: 404,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
tc.LogIn().Get(fmt.Sprintf("/m/uuid/i/%s", i.Uuid)).
|
||||
AssertStatusCode(http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("incident is visible", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
m, _ := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
i, _ := tc.Store.CreateIncident(t.Context(), uptimemonitor.Incident{
|
||||
MonitorID: 1,
|
||||
StatusCode: 404,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
tc.LogIn().
|
||||
Get(fmt.Sprintf("/m/%s/i/%s", m.Uuid, i.Uuid)).
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertSeeText("404 Not Found")
|
||||
})
|
||||
|
||||
}
|
115
test/login_test.go
Normal file
115
test/login_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
t.Run("setup is required before user can log in", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/login").AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
tc.Post("/login", nil).AssertStatusCode(http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("shows a login form", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
tc.Get("/login").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`form[hx-post="/login"]`)
|
||||
})
|
||||
|
||||
t.Run("validates form", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Post("/login", nil).
|
||||
AssertStatusCode(http.StatusBadRequest).
|
||||
AssertSeeText("The email is required").
|
||||
AssertSeeText("The password is required")
|
||||
|
||||
tc.Post("/login", url.Values{
|
||||
"email": []string{"invalid"},
|
||||
}).
|
||||
AssertStatusCode(http.StatusBadRequest).
|
||||
AssertSeeText("The email format is invalid")
|
||||
})
|
||||
|
||||
t.Run("user has to exist", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Post("/login", url.Values{
|
||||
"email": []string{"other@example.com"},
|
||||
"password": []string{"password"},
|
||||
}).
|
||||
AssertStatusCode(http.StatusBadRequest).
|
||||
AssertSeeText("The credentials do not match our records")
|
||||
})
|
||||
|
||||
t.Run("password has to be valid", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Post("/login", url.Values{
|
||||
"email": []string{"test@example.com"},
|
||||
"password": []string{"invalid"},
|
||||
}).
|
||||
AssertStatusCode(http.StatusBadRequest).
|
||||
AssertSeeText("The credentials do not match our records")
|
||||
})
|
||||
|
||||
t.Run("user can log in", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Get("/").AssertRedirect(http.StatusSeeOther, "/login")
|
||||
|
||||
ar := tc.Post("/login", url.Values{
|
||||
"email": []string{"test@example.com"},
|
||||
"password": []string{"password"},
|
||||
}).
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertCookieSet("session").
|
||||
AssertHeader("HX-Redirect", "/")
|
||||
|
||||
tc.AssertDatabaseCount("sessions", 1)
|
||||
|
||||
cookies := ar.Response.Cookies()
|
||||
var cookie *http.Cookie
|
||||
|
||||
for _, c := range cookies {
|
||||
if c.Name == "session" {
|
||||
cookie = c
|
||||
}
|
||||
}
|
||||
|
||||
tc.WithCookie(cookie).
|
||||
Get("/new").
|
||||
AssertNoRedirect().
|
||||
AssertStatusCode(http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("logged in users are redirected to a new page", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Get("/login").
|
||||
AssertRedirect(http.StatusSeeOther, "/new")
|
||||
})
|
||||
}
|
22
test/logout_test.go
Normal file
22
test/logout_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogout(t *testing.T) {
|
||||
t.Run("user can log out", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Get("/logout").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertRedirect(http.StatusSeeOther, "/login")
|
||||
|
||||
tc.AssertDatabaseCount("sessions", 0)
|
||||
|
||||
tc.Get("/").AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
}
|
27
test/middleware_test.go
Normal file
27
test/middleware_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMiddleware(t *testing.T) {
|
||||
t.Run("panic recoverer", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/test/panic").AssertStatusCode(http.StatusInternalServerError)
|
||||
})
|
||||
|
||||
t.Run("cache test", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Get("/").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertHeader("Cache-Control", "no-cache, no-store, must-revalidate").
|
||||
AssertHeader("Pragma", "no-cache").
|
||||
AssertHeader("Expires", "0")
|
||||
})
|
||||
}
|
285
test/monitor_test.go
Normal file
285
test/monitor_test.go
Normal file
@@ -0,0 +1,285 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/service"
|
||||
)
|
||||
|
||||
func TestMonitor_ListMonitors(t *testing.T) {
|
||||
t.Run("setup is required", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/monitors").
|
||||
AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
})
|
||||
|
||||
t.Run("logged user is required", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Get("/monitors").
|
||||
AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
|
||||
t.Run("monitors table is visible on home page", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: "https://example.com"})
|
||||
|
||||
tc.LogIn().
|
||||
Get("/").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`div[hx-get="/monitors"]`)
|
||||
})
|
||||
|
||||
t.Run("empty monitors list", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Get("/monitors").
|
||||
AssertNoRedirect().
|
||||
AssertStatusCode(http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("list monitors", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: "https://example.com"})
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{Url: "https://example.com/123"})
|
||||
|
||||
tc.LogIn().
|
||||
Get("/monitors").
|
||||
AssertSeeText("example.com").
|
||||
AssertSeeText("example.com/123")
|
||||
})
|
||||
}
|
||||
|
||||
func TestMonitor_CreateMonitor(t *testing.T) {
|
||||
t.Run("setup is required", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Post("/monitors", url.Values{}).
|
||||
AssertStatusCode(http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("user has to be logged in", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Post("/monitors", url.Values{}).
|
||||
AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
|
||||
t.Run("monitor form is visible", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Get("/new").
|
||||
AssertNoRedirect().
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`form[hx-post="/monitors"]`).
|
||||
AssertElementVisible(`select[name="http_method"]`).
|
||||
AssertElementVisible(`input[name="url"]`)
|
||||
})
|
||||
|
||||
t.Run("url is required", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Post("/monitors", url.Values{}).
|
||||
AssertNoRedirect().
|
||||
AssertStatusCode(http.StatusBadRequest).
|
||||
AssertSeeText("The url is required")
|
||||
})
|
||||
|
||||
t.Run("the url has to be a valid url", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Post("/monitors", url.Values{
|
||||
"url": []string{"invalid"},
|
||||
}).
|
||||
AssertStatusCode(http.StatusBadRequest).
|
||||
AssertSeeText("The url is invalid")
|
||||
})
|
||||
|
||||
t.Run("the url can be created", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
res := tc.LogIn().
|
||||
Post("/monitors", url.Values{
|
||||
"http_method": []string{"GET"},
|
||||
"has_custom_headers": []string{"on"},
|
||||
"http_headers": []string{`{"test":"abc"}`},
|
||||
"has_custom_body": []string{"on"},
|
||||
"http_body": []string{`{"test":"123"}`},
|
||||
"url": []string{"https://example.com"},
|
||||
}).
|
||||
AssertStatusCode(http.StatusOK)
|
||||
|
||||
m, _ := tc.Store.GetMonitorByID(t.Context(), 1)
|
||||
|
||||
res.AssertHeader("HX-Redirect", fmt.Sprintf("/m/%s", m.Uuid))
|
||||
tc.AssertDatabaseCount("monitors", 1)
|
||||
tc.Get("/monitors").AssertSeeText("example.com")
|
||||
|
||||
tc.AssertEqual(m.Url, "https://example.com")
|
||||
tc.AssertEqual(m.HttpMethod, "GET")
|
||||
tc.AssertEqual(m.HttpHeaders, `{"test":"abc"}`)
|
||||
tc.AssertEqual(m.HttpBody, `{"test":"123"}`)
|
||||
})
|
||||
|
||||
t.Run("custom headers are validated when present", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Post("/monitors", url.Values{
|
||||
"http_method": []string{"GET"},
|
||||
"url": []string{"https://example.com"},
|
||||
}).
|
||||
AssertStatusCode(http.StatusOK)
|
||||
|
||||
tc.Post("/monitors", url.Values{
|
||||
"http_method": []string{"GET"},
|
||||
"url": []string{"https://example.com"},
|
||||
"has_custom_headers": []string{"on"},
|
||||
"http_headers": []string{`INVALID JSON`},
|
||||
}).
|
||||
AssertStatusCode(http.StatusBadRequest).
|
||||
AssertSeeText("The http headers should be a valid JSON")
|
||||
})
|
||||
}
|
||||
|
||||
func TestMonitor_MonitorPage(t *testing.T) {
|
||||
t.Run("setup is required", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/m/123").AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
})
|
||||
|
||||
t.Run("guests cannot view monitors", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
tc.Get("/m/123").AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
|
||||
t.Run("monitor has to exist", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().Get("/m/123").AssertStatusCode(http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("monitor can be viewed", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
m, _ := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.LogIn().Get(fmt.Sprintf("/m/%s", m.Uuid)).AssertStatusCode(http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMonitor_RemoveMonitor(t *testing.T) {
|
||||
t.Run("guests cannot remove monitors", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/m/uuid/delete").AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
tc.Delete("/monitors/1").AssertStatusCode(http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("monitor has to exist", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn()
|
||||
|
||||
tc.Get("/m/uuid/delete").AssertStatusCode(http.StatusNotFound)
|
||||
tc.Delete("/monitors/1").AssertStatusCode(http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("delete monitor form is present", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
m, _ := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
HttpMethod: http.MethodGet,
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.LogIn().
|
||||
Get(fmt.Sprintf("/m/%s/delete", m.Uuid)).
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(fmt.Sprintf(`form[hx-delete="/monitors/%d"]`, m.ID))
|
||||
})
|
||||
|
||||
t.Run("monitor can be removed", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
HttpMethod: http.MethodGet,
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.LogIn().
|
||||
Delete("/monitors/1").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertHeader("HX-Redirect", "/")
|
||||
|
||||
tc.AssertDatabaseCount("monitors", 0)
|
||||
})
|
||||
|
||||
t.Run("whe monitor is removed, checks and incidents are also removed", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
HttpMethod: http.MethodGet,
|
||||
Url: tc.Server.URL + "/test/500",
|
||||
})
|
||||
|
||||
service := service.New(tc.Store)
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
tc.AssertDatabaseCount("checks", 1)
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
|
||||
tc.LogIn().
|
||||
Delete("/monitors/1").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertHeader("HX-Redirect", "/")
|
||||
|
||||
tc.AssertDatabaseCount("checks", 0)
|
||||
tc.AssertDatabaseCount("incidents", 0)
|
||||
})
|
||||
}
|
82
test/setup_test.go
Normal file
82
test/setup_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"uptimemonitor"
|
||||
)
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
t.Run("redirects to setup when no users are found", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/").
|
||||
AssertRedirect(http.StatusSeeOther, "/setup")
|
||||
})
|
||||
|
||||
t.Run("redirects to login page when users are found", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateUser(t.Context(), uptimemonitor.User{
|
||||
Name: "Test User",
|
||||
Email: "test@example.com",
|
||||
})
|
||||
|
||||
tc.Get("/setup").
|
||||
AssertRedirect(http.StatusSeeOther, "/login")
|
||||
})
|
||||
|
||||
t.Run("shows a setup form when no users are found", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/setup").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`form[hx-post="/setup"]`).
|
||||
AssertElementVisible(`input[name="name"]`).
|
||||
AssertElementVisible(`input[name="email"]`).
|
||||
AssertElementVisible(`input[name="password"]`).
|
||||
AssertElementVisible(`button[type="submit"]`)
|
||||
})
|
||||
|
||||
t.Run("validates a form", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Post("/setup", url.Values{}).
|
||||
AssertStatusCode(http.StatusBadRequest).
|
||||
AssertElementVisible(`form[hx-swap="outerHTML"]`).
|
||||
AssertSeeText("The name is required").
|
||||
AssertSeeText("The email is required").
|
||||
AssertSeeText("The password is required")
|
||||
|
||||
res := tc.Post("/setup", url.Values{
|
||||
"email": []string{"invalid"},
|
||||
})
|
||||
|
||||
res.AssertStatusCode(http.StatusBadRequest).
|
||||
AssertElementVisible(`form[hx-swap="outerHTML"]`).
|
||||
AssertSeeText("The email format is invalid")
|
||||
})
|
||||
|
||||
t.Run("setup", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.AssertDatabaseCount("users", 0)
|
||||
|
||||
res := tc.Post("/setup", url.Values{
|
||||
"name": []string{"Test"},
|
||||
"email": []string{"test@example.com"},
|
||||
"password": []string{"password"},
|
||||
})
|
||||
|
||||
res.AssertHeader("HX-Redirect", "/")
|
||||
tc.AssertDatabaseCount("users", 1)
|
||||
|
||||
tc.Get("/setup").AssertRedirect(http.StatusSeeOther, "/new")
|
||||
})
|
||||
}
|
26
test/sponsor_test.go
Normal file
26
test/sponsor_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSponsor(t *testing.T) {
|
||||
t.Run("sponsors are lazy loaded", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Get("/sponsors").
|
||||
AssertStatusCode(http.StatusOK).
|
||||
AssertElementVisible(`div[hx-get="/sponsors"]`)
|
||||
})
|
||||
|
||||
t.Run("sponsors are loaded via api", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.WithHeader("HX-Request", "true").
|
||||
Get("/sponsors").
|
||||
AssertSeeText("AIR Labs")
|
||||
})
|
||||
}
|
349
test/test_case.go
Normal file
349
test/test_case.go
Normal file
@@ -0,0 +1,349 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/handler"
|
||||
"uptimemonitor/pkg/testutil"
|
||||
"uptimemonitor/router"
|
||||
"uptimemonitor/service"
|
||||
"uptimemonitor/store"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var TestWebhookCalledCount int64
|
||||
var TestWebhookBody string
|
||||
var ExpectedWebhookBody string
|
||||
var ExpectedWebhookHeaderKey string
|
||||
var ExpectedWebhookHeaderValue string
|
||||
|
||||
type TestCase struct {
|
||||
T *testing.T
|
||||
Server *httptest.Server
|
||||
Client *http.Client
|
||||
Store *store.Store
|
||||
User *uptimemonitor.User
|
||||
Headers map[string]string
|
||||
Cookies []*http.Cookie
|
||||
}
|
||||
|
||||
func NewTestCase(t *testing.T) *TestCase {
|
||||
store := store.New(":memory:")
|
||||
service := service.New(store)
|
||||
handler := handler.New(store, service, false)
|
||||
router := router.New(handler, registerRoutes)
|
||||
server := httptest.NewServer(router)
|
||||
|
||||
TestWebhookBody = ""
|
||||
TestWebhookCalledCount = 0
|
||||
ExpectedWebhookBody = ""
|
||||
ExpectedWebhookHeaderKey = ""
|
||||
ExpectedWebhookHeaderValue = ""
|
||||
|
||||
return &TestCase{
|
||||
T: t,
|
||||
Server: server,
|
||||
Client: server.Client(),
|
||||
Store: store,
|
||||
Headers: map[string]string{},
|
||||
Cookies: []*http.Cookie{},
|
||||
}
|
||||
}
|
||||
|
||||
func registerRoutes(router *http.ServeMux) {
|
||||
router.HandleFunc("GET /test/200", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
router.HandleFunc("GET /test/404", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
router.HandleFunc("GET /test/500", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
})
|
||||
|
||||
router.HandleFunc("GET /test/timeout", func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(30 * time.Second)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
router.HandleFunc("GET /test/panic", func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("test")
|
||||
})
|
||||
|
||||
i := 0
|
||||
router.HandleFunc("GET /test/even", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
i++
|
||||
}()
|
||||
|
||||
if i%2 == 0 {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
router.HandleFunc("POST /test/post", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
router.HandleFunc("PATCH /test/patch", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
router.HandleFunc("PUT /test/put", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
router.HandleFunc("DELETE /test/delete", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
router.HandleFunc("POST /test/body", func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if string(body) != `{"test":123}` {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
router.HandleFunc("POST /test/headers", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("test") != "abc" {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
router.HandleFunc("POST /test/webhook", func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if ExpectedWebhookBody != "" && string(body) != ExpectedWebhookBody {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if ExpectedWebhookHeaderKey != "" && r.Header.Get(ExpectedWebhookHeaderKey) != ExpectedWebhookHeaderValue {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
TestWebhookCalledCount++
|
||||
TestWebhookBody = string(body)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func (tc *TestCase) Close() {
|
||||
tc.Server.Close()
|
||||
}
|
||||
|
||||
func (tc *TestCase) WithHeader(key, value string) *TestCase {
|
||||
tc.Headers[key] = value
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
func (tc *TestCase) WithCookie(c *http.Cookie) *TestCase {
|
||||
tc.Cookies = append(tc.Cookies, c)
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
func (tc *TestCase) Get(url string) *testutil.AssertableResponse {
|
||||
req, err := http.NewRequest(http.MethodGet, tc.Server.URL+url, nil)
|
||||
if err != nil {
|
||||
tc.T.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(tc.Cookies) > 0 {
|
||||
for _, c := range tc.Cookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
}
|
||||
|
||||
if len(tc.Headers) > 0 {
|
||||
for k, v := range tc.Headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := tc.Client.Do(req)
|
||||
if err != nil {
|
||||
tc.T.Fatalf("failed to get %s: %v", url, err)
|
||||
}
|
||||
|
||||
return testutil.NewAssertableResponse(tc.T, res)
|
||||
}
|
||||
|
||||
func (tc *TestCase) Post(url string, data url.Values) *testutil.AssertableResponse {
|
||||
req, err := http.NewRequest(http.MethodPost, tc.Server.URL+url, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
tc.T.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
if len(tc.Cookies) > 0 {
|
||||
for _, c := range tc.Cookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := tc.Client.Do(req)
|
||||
if err != nil {
|
||||
tc.T.Fatalf("failed to post %s: %v", url, err)
|
||||
}
|
||||
|
||||
for _, c := range res.Cookies() {
|
||||
if c.Name == "session" {
|
||||
tc.Cookies = append(tc.Cookies, c)
|
||||
}
|
||||
}
|
||||
|
||||
return testutil.NewAssertableResponse(tc.T, res)
|
||||
}
|
||||
|
||||
func (tc *TestCase) Patch(url string, data url.Values) *testutil.AssertableResponse {
|
||||
req, err := http.NewRequest(http.MethodPatch, tc.Server.URL+url, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
tc.T.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
if len(tc.Cookies) > 0 {
|
||||
for _, c := range tc.Cookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := tc.Client.Do(req)
|
||||
if err != nil {
|
||||
tc.T.Fatalf("failed to post %s: %v", url, err)
|
||||
}
|
||||
|
||||
return testutil.NewAssertableResponse(tc.T, res)
|
||||
}
|
||||
|
||||
func (tc *TestCase) Delete(url string) *testutil.AssertableResponse {
|
||||
req, err := http.NewRequest(http.MethodDelete, tc.Server.URL+url, nil)
|
||||
if err != nil {
|
||||
tc.T.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(tc.Cookies) > 0 {
|
||||
for _, c := range tc.Cookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := tc.Client.Do(req)
|
||||
if err != nil {
|
||||
tc.T.Fatalf("failed to delete %s: %v", url, err)
|
||||
}
|
||||
|
||||
return testutil.NewAssertableResponse(tc.T, res)
|
||||
}
|
||||
|
||||
func (tc *TestCase) AssertDatabaseCount(table string, expected int) *TestCase {
|
||||
tc.T.Helper()
|
||||
|
||||
stmt := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, table)
|
||||
var count int
|
||||
|
||||
err := tc.Store.DB().QueryRow(stmt).Scan(&count)
|
||||
if err != nil {
|
||||
tc.T.Fatalf("failed to count rows from table '%s', error: %v", table, err)
|
||||
}
|
||||
|
||||
if count != expected {
|
||||
tc.T.Fatalf("expected to find %d number of rows in a table '%s, but found %d", expected, table, count)
|
||||
}
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
func (tc *TestCase) CreateTestUser(email, password string) *TestCase {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
tc.T.Fatalf("unexpected bcrypt error: %v", err)
|
||||
}
|
||||
|
||||
user, err := tc.Store.CreateUser(tc.T.Context(), uptimemonitor.User{
|
||||
Name: "Test User",
|
||||
Email: email,
|
||||
PasswordHash: string(hash),
|
||||
})
|
||||
if err != nil {
|
||||
tc.T.Fatalf("unable to create test user: %v", err)
|
||||
}
|
||||
|
||||
tc.User = &user
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
func (tc *TestCase) LogIn() *TestCase {
|
||||
tc.CreateTestUser("test@example.com", "password")
|
||||
|
||||
session, err := tc.Store.CreateSession(tc.T.Context(), uptimemonitor.Session{
|
||||
User: *tc.User,
|
||||
UserID: tc.User.ID,
|
||||
ExpiresAt: time.Now().Add(time.Hour),
|
||||
})
|
||||
if err != nil {
|
||||
tc.T.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
c := &http.Cookie{
|
||||
Name: "session",
|
||||
Value: session.Uuid,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Secure: false,
|
||||
Expires: session.ExpiresAt,
|
||||
}
|
||||
|
||||
return tc.WithCookie(c)
|
||||
}
|
||||
|
||||
func (tc *TestCase) AssertEqual(a, b any) *TestCase {
|
||||
tc.T.Helper()
|
||||
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
tc.T.Fatalf(`expected "%v" to be equal to "%v"`, a, b)
|
||||
}
|
||||
return tc
|
||||
}
|
||||
|
||||
func (tc *TestCase) AssertNoError(err error) *TestCase {
|
||||
tc.T.Helper()
|
||||
|
||||
if err != nil {
|
||||
tc.T.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
return tc
|
||||
}
|
110
test/uptime_test.go
Normal file
110
test/uptime_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"uptimemonitor"
|
||||
)
|
||||
|
||||
func TestUptime(t *testing.T) {
|
||||
t.Run("uptime is empty when there are no checks", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
m, _ := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.AssertEqual(m.Uptime, float32(0))
|
||||
tc.AssertEqual(m.AvgResponseTimeMs, int64(0))
|
||||
})
|
||||
|
||||
t.Run("uptime is computed when check is created", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.Store.CreateCheck(t.Context(), uptimemonitor.Check{
|
||||
MonitorID: 1,
|
||||
StatusCode: 200,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
m, _ := tc.Store.GetMonitorByID(t.Context(), 1)
|
||||
|
||||
tc.AssertEqual(m.N, int64(1))
|
||||
tc.AssertEqual(m.Uptime, float32(100))
|
||||
tc.AssertEqual(m.AvgResponseTimeMs, int64(100))
|
||||
})
|
||||
|
||||
t.Run("it works with multiple checks", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.Store.CreateCheck(t.Context(), uptimemonitor.Check{
|
||||
MonitorID: 1,
|
||||
StatusCode: 200,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
tc.Store.CreateCheck(t.Context(), uptimemonitor.Check{
|
||||
MonitorID: 1,
|
||||
StatusCode: 200,
|
||||
ResponseTimeMs: 200,
|
||||
})
|
||||
|
||||
m, _ := tc.Store.GetMonitorByID(t.Context(), 1)
|
||||
|
||||
tc.AssertEqual(m.N, int64(2))
|
||||
tc.AssertEqual(m.Uptime, float32(100))
|
||||
tc.AssertEqual(m.AvgResponseTimeMs, int64(150))
|
||||
})
|
||||
|
||||
t.Run("uptime is updated", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: "http://example.com",
|
||||
})
|
||||
|
||||
tc.Store.CreateCheck(t.Context(), uptimemonitor.Check{
|
||||
MonitorID: 1,
|
||||
StatusCode: 404,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
m, _ := tc.Store.GetMonitorByID(t.Context(), 1)
|
||||
|
||||
tc.AssertEqual(m.N, int64(1))
|
||||
tc.AssertEqual(m.Uptime, float32(0))
|
||||
|
||||
tc.Store.CreateCheck(t.Context(), uptimemonitor.Check{
|
||||
MonitorID: 1,
|
||||
StatusCode: 200,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
m, _ = tc.Store.GetMonitorByID(t.Context(), 1)
|
||||
|
||||
tc.AssertEqual(m.N, int64(2))
|
||||
tc.AssertEqual(m.Uptime, float32(50))
|
||||
|
||||
tc.Store.CreateCheck(t.Context(), uptimemonitor.Check{
|
||||
MonitorID: 1,
|
||||
StatusCode: 500,
|
||||
ResponseTimeMs: 100,
|
||||
})
|
||||
|
||||
m, _ = tc.Store.GetMonitorByID(t.Context(), 1)
|
||||
|
||||
tc.AssertEqual(m.N, int64(3))
|
||||
tc.AssertEqual(m.Uptime, float32(33.3))
|
||||
})
|
||||
}
|
162
test/webhook_test.go
Normal file
162
test/webhook_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/form"
|
||||
"uptimemonitor/service"
|
||||
)
|
||||
|
||||
func TestWebhook_SaveWebhook(t *testing.T) {
|
||||
t.Run("webhook is validated", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
f := form.MonitorForm{
|
||||
Url: "https://valid.url",
|
||||
HttpMethod: http.MethodGet,
|
||||
|
||||
HasWebhook: true,
|
||||
WebhookUrl: "invalid",
|
||||
WebhookHeaders: "invalid json",
|
||||
WebhookBody: "data",
|
||||
}
|
||||
|
||||
tc.AssertEqual(false, f.Validate())
|
||||
})
|
||||
|
||||
t.Run("webhook info can be saved when creating a monitor", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().
|
||||
Post("/monitors", url.Values{
|
||||
"http_method": []string{"GET"},
|
||||
"url": []string{"https://example.com"},
|
||||
"has_webhook": []string{"on"},
|
||||
"webhook_url": []string{tc.Server.URL + "/test/webhook"},
|
||||
"webhook_method": []string{"POST"},
|
||||
"webhook_headers": []string{`{"test":"abc"}`},
|
||||
"webhook_body": []string{`{"test":"123"}`},
|
||||
}).AssertStatusCode(http.StatusOK)
|
||||
|
||||
m, _ := tc.Store.GetMonitorByID(t.Context(), 1)
|
||||
|
||||
tc.AssertEqual(m.WebhookUrl, tc.Server.URL+"/test/webhook")
|
||||
tc.AssertEqual(m.WebhookMethod, "POST")
|
||||
tc.AssertEqual(m.WebhookHeaders, `{"test":"abc"}`)
|
||||
tc.AssertEqual(m.WebhookBody, `{"test":"123"}`)
|
||||
})
|
||||
|
||||
t.Run("webhook data can be updated", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
_, err := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
HttpMethod: http.MethodGet,
|
||||
Url: "https://google.com",
|
||||
})
|
||||
|
||||
tc.AssertNoError(err)
|
||||
|
||||
tc.LogIn().
|
||||
Patch("/monitors/1", url.Values{
|
||||
"http_method": []string{"GET"},
|
||||
"url": []string{"https://example.com"},
|
||||
"has_webhook": []string{"on"},
|
||||
"webhook_url": []string{tc.Server.URL + "/test/webhook"},
|
||||
"webhook_method": []string{"POST"},
|
||||
"webhook_headers": []string{`{"test":"abc"}`},
|
||||
"webhook_body": []string{`{"test":"123"}`},
|
||||
}).AssertStatusCode(http.StatusOK)
|
||||
|
||||
m, err := tc.Store.GetMonitorByID(t.Context(), 1)
|
||||
tc.AssertNoError(err)
|
||||
|
||||
tc.AssertEqual(m.WebhookUrl, tc.Server.URL+"/test/webhook")
|
||||
tc.AssertEqual(m.WebhookMethod, "POST")
|
||||
tc.AssertEqual(m.WebhookHeaders, `{"test":"abc"}`)
|
||||
tc.AssertEqual(m.WebhookBody, `{"test":"123"}`)
|
||||
})
|
||||
|
||||
t.Run("webhook fields are present in the forms", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
tc.LogIn().Get("/new").
|
||||
AssertElementVisible(`input[name="has_webhook"]`).
|
||||
AssertElementVisible(`select[name="webhook_method"]`).
|
||||
AssertElementVisible(`textarea[name="webhook_body"]`).
|
||||
AssertElementVisible(`textarea[name="webhook_headers"]`)
|
||||
|
||||
m, _ := tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
HttpMethod: http.MethodGet,
|
||||
Url: "https://google.com",
|
||||
})
|
||||
|
||||
tc.Get(fmt.Sprintf("/m/%s/edit", m.Uuid)).
|
||||
AssertElementVisible(`input[name="has_webhook"]`).
|
||||
AssertElementVisible(`select[name="webhook_method"]`).
|
||||
AssertElementVisible(`textarea[name="webhook_body"]`).
|
||||
AssertElementVisible(`textarea[name="webhook_headers"]`)
|
||||
})
|
||||
|
||||
t.Run("webhook is called on incident", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
ExpectedWebhookBody = `{"test":123}`
|
||||
ExpectedWebhookHeaderKey = "test"
|
||||
ExpectedWebhookHeaderValue = "abc"
|
||||
|
||||
service := service.New(tc.Store)
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: tc.Server.URL + "/test/500",
|
||||
WebhookMethod: http.MethodPost,
|
||||
WebhookUrl: tc.Server.URL + "/test/webhook",
|
||||
WebhookHeaders: `{"test":"abc"}`,
|
||||
WebhookBody: `{"test":123}`,
|
||||
})
|
||||
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
tc.AssertDatabaseCount("checks", 1)
|
||||
|
||||
tc.AssertEqual(int64(TestWebhookCalledCount), int64(1))
|
||||
})
|
||||
|
||||
t.Run("webhook can have parsed body", func(t *testing.T) {
|
||||
tc := NewTestCase(t)
|
||||
defer tc.Close()
|
||||
|
||||
service := service.New(tc.Store)
|
||||
url := tc.Server.URL + "/test/500"
|
||||
|
||||
tc.Store.CreateMonitor(t.Context(), uptimemonitor.Monitor{
|
||||
Url: url,
|
||||
WebhookMethod: http.MethodPost,
|
||||
WebhookUrl: tc.Server.URL + "/test/webhook",
|
||||
WebhookBody: `{{.Url}},{{ .StatusCode}}`,
|
||||
})
|
||||
|
||||
ch := service.StartCheck()
|
||||
service.RunCheck(t.Context(), ch)
|
||||
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
tc.AssertDatabaseCount("incidents", 1)
|
||||
tc.AssertDatabaseCount("checks", 1)
|
||||
|
||||
tc.AssertEqual(int64(TestWebhookCalledCount), int64(1))
|
||||
tc.AssertEqual(TestWebhookBody, fmt.Sprintf("%s,%d", url, 500))
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user