refactor(models): split Site into SiteConfig + SiteState
Site now embeds SiteConfig (22 persistent fields) and SiteState (11 ephemeral runtime fields). Field access unchanged via promotion — site.Name and site.Status still work. Store layer deals exclusively in SiteConfig — the DB never sees runtime state. Engine's liveState keeps full Site composites. UpdateSiteConfig reduced from 11-line field-by-field copy to `existing.SiteConfig = cfg`. RunCheck takes SiteConfig (only needs config fields). Checker is now statically prevented from reading/writing runtime state. Backup.Sites changed to []SiteConfig — exports no longer carry zero-valued runtime fields. Import backward-compatible (json ignores unknown fields).
This commit was merged in pull request #109.
This commit is contained in:
+1
-1
@@ -474,7 +474,7 @@ func seedDemoData(s store.Store) {
|
|||||||
alertID = alerts[0].ID
|
alertID = alerts[0].ID
|
||||||
}
|
}
|
||||||
|
|
||||||
demoSites := []models.Site{
|
demoSites := []models.SiteConfig{
|
||||||
{Name: "Google", URL: "https://www.google.com", Type: "http", Interval: 30, AlertID: alertID, CheckSSL: true, ExpiryThreshold: 14, MaxRetries: 2},
|
{Name: "Google", URL: "https://www.google.com", Type: "http", Interval: 30, AlertID: alertID, CheckSSL: true, ExpiryThreshold: 14, MaxRetries: 2},
|
||||||
{Name: "GitHub", URL: "https://github.com", Type: "http", Interval: 30, AlertID: alertID, CheckSSL: true, ExpiryThreshold: 7, MaxRetries: 3},
|
{Name: "GitHub", URL: "https://github.com", Type: "http", Interval: 30, AlertID: alertID, CheckSSL: true, ExpiryThreshold: 7, MaxRetries: 3},
|
||||||
{Name: "Cloudflare DNS", URL: "https://1.1.1.1", Type: "http", Interval: 60, AlertID: alertID, ExpiryThreshold: 7, MaxRetries: 1},
|
{Name: "Cloudflare DNS", URL: "https://1.1.1.1", Type: "http", Interval: 60, AlertID: alertID, ExpiryThreshold: 7, MaxRetries: 1},
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ func TestProbeRegister_Failure(t *testing.T) {
|
|||||||
func TestProbeFetchAssignments_Success(t *testing.T) {
|
func TestProbeFetchAssignments_Success(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string][]models.Site{
|
json.NewEncoder(w).Encode(map[string][]models.Site{
|
||||||
"sites": {{ID: 1, Name: "s1", Type: "http", URL: "http://example.com"}},
|
"sites": {{SiteConfig: models.SiteConfig{ID: 1, Name: "s1", Type: "http", URL: "http://example.com"}}},
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
@@ -240,8 +240,8 @@ func TestProbeExecuteChecks(t *testing.T) {
|
|||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
sites := []models.Site{
|
sites := []models.Site{
|
||||||
{ID: 1, Type: "http", URL: srv.URL},
|
{SiteConfig: models.SiteConfig{ID: 1, Type: "http", URL: srv.URL}},
|
||||||
{ID: 2, Type: "http", URL: srv.URL},
|
{SiteConfig: models.SiteConfig{ID: 2, Type: "http", URL: srv.URL}},
|
||||||
}
|
}
|
||||||
|
|
||||||
strict := &http.Client{}
|
strict := &http.Client{}
|
||||||
@@ -277,7 +277,7 @@ func TestProbeExecuteChecks_Concurrency(t *testing.T) {
|
|||||||
|
|
||||||
var sites []models.Site
|
var sites []models.Site
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
sites = append(sites, models.Site{ID: i + 1, Type: "http", URL: srv.URL})
|
sites = append(sites, models.Site{SiteConfig: models.SiteConfig{ID: i + 1, Type: "http", URL: srv.URL}})
|
||||||
}
|
}
|
||||||
|
|
||||||
results := probeExecuteChecks(context.Background(), sites, &http.Client{}, &http.Client{}, true)
|
results := probeExecuteChecks(context.Background(), sites, &http.Client{}, &http.Client{}, true)
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ loop:
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
defer func() { <-sem }()
|
defer func() { <-sem }()
|
||||||
|
|
||||||
cr := monitor.RunCheck(ctx, s, strict, insecure, false, allowPrivate)
|
cr := monitor.RunCheck(ctx, s.SiteConfig, strict, insecure, false, allowPrivate)
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
results = append(results, probeResultItem{
|
results = append(results, probeResultItem{
|
||||||
SiteID: s.ID,
|
SiteID: s.ID,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func Apply(ctx context.Context, s store.Store, f *File, opts ApplyOpts) ([]Chang
|
|||||||
existingAlertsByName[a.Name] = a
|
existingAlertsByName[a.Name] = a
|
||||||
}
|
}
|
||||||
|
|
||||||
existingSitesByName := make(map[string]models.Site, len(existingSites))
|
existingSitesByName := make(map[string]models.SiteConfig, len(existingSites))
|
||||||
for _, s := range existingSites {
|
for _, s := range existingSites {
|
||||||
existingSitesByName[s.Name] = s
|
existingSitesByName[s.Name] = s
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ func Apply(ctx context.Context, s store.Store, f *File, opts ApplyOpts) ([]Chang
|
|||||||
return changes, nil
|
return changes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyMonitor(ctx context.Context, s store.Store, m Monitor, alertMap map[string]int, existing map[string]models.Site, parentID int, dryRun bool) ([]Change, error) {
|
func applyMonitor(ctx context.Context, s store.Store, m Monitor, alertMap map[string]int, existing map[string]models.SiteConfig, parentID int, dryRun bool) ([]Change, error) {
|
||||||
alertID, err := resolveAlertID(alertMap, m.Alert)
|
alertID, err := resolveAlertID(alertMap, m.Alert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("monitor %q: %w", m.Name, err)
|
return nil, fmt.Errorf("monitor %q: %w", m.Name, err)
|
||||||
@@ -222,8 +222,8 @@ func resolveAlertID(alertMap map[string]int, name string) (int, error) {
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func monitorToSite(m Monitor, alertID, parentID int) models.Site {
|
func monitorToSite(m Monitor, alertID, parentID int) models.SiteConfig {
|
||||||
s := models.Site{
|
s := models.SiteConfig{
|
||||||
Name: m.Name,
|
Name: m.Name,
|
||||||
Type: m.Type,
|
Type: m.Type,
|
||||||
URL: m.URL,
|
URL: m.URL,
|
||||||
@@ -269,7 +269,7 @@ func collectMonitorNames(monitors []Monitor, names map[string]bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeSite(s models.Site) models.Site {
|
func normalizeSite(s models.SiteConfig) models.SiteConfig {
|
||||||
if s.Method == "" {
|
if s.Method == "" {
|
||||||
s.Method = "GET"
|
s.Method = "GET"
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,7 @@ func diffAlert(existing models.AlertConfig, desired Alert) string {
|
|||||||
return strings.Join(diffs, ", ")
|
return strings.Join(diffs, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func diffSite(existing, desired models.Site) string {
|
func diffSite(existing, desired models.SiteConfig) string {
|
||||||
var diffs []string
|
var diffs []string
|
||||||
if existing.URL != desired.URL {
|
if existing.URL != desired.URL {
|
||||||
diffs = append(diffs, fmt.Sprintf("url: %s -> %s", existing.URL, desired.URL))
|
diffs = append(diffs, fmt.Sprintf("url: %s -> %s", existing.URL, desired.URL))
|
||||||
|
|||||||
@@ -114,8 +114,8 @@ func TestApplyUpdate(t *testing.T) {
|
|||||||
|
|
||||||
func TestApplyPrune(t *testing.T) {
|
func TestApplyPrune(t *testing.T) {
|
||||||
s := newTestStore(t)
|
s := newTestStore(t)
|
||||||
s.AddSite(context.Background(), models.Site{Name: "Keep", URL: "https://keep.com", Type: "http", Interval: 30, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
s.AddSite(context.Background(), models.SiteConfig{Name: "Keep", URL: "https://keep.com", Type: "http", Interval: 30, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
||||||
s.AddSite(context.Background(), models.Site{Name: "Remove", URL: "https://remove.com", Type: "http", Interval: 30, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
s.AddSite(context.Background(), models.SiteConfig{Name: "Remove", URL: "https://remove.com", Type: "http", Interval: 30, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
||||||
|
|
||||||
f := &File{
|
f := &File{
|
||||||
Monitors: []Monitor{
|
Monitors: []Monitor{
|
||||||
@@ -191,7 +191,7 @@ func TestApplyGroupHierarchy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sites, _ := s.GetSites(context.Background())
|
sites, _ := s.GetSites(context.Background())
|
||||||
var group models.Site
|
var group models.SiteConfig
|
||||||
for _, s := range sites {
|
for _, s := range sites {
|
||||||
if s.Type == "group" {
|
if s.Type == "group" {
|
||||||
group = s
|
group = s
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ func Export(ctx context.Context, s store.Store) (*File, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
groups := make(map[int]models.Site)
|
groups := make(map[int]models.SiteConfig)
|
||||||
children := make(map[int][]models.Site)
|
children := make(map[int][]models.SiteConfig)
|
||||||
var topLevel []models.Site
|
var topLevel []models.SiteConfig
|
||||||
|
|
||||||
for _, s := range dbSites {
|
for _, s := range dbSites {
|
||||||
switch {
|
switch {
|
||||||
@@ -76,7 +76,7 @@ func Export(ctx context.Context, s store.Store) (*File, error) {
|
|||||||
return &File{Alerts: yamlAlerts, Monitors: yamlMonitors}, nil
|
return &File{Alerts: yamlAlerts, Monitors: yamlMonitors}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func siteToMonitor(s models.Site, alertIDToName map[int]string) Monitor {
|
func siteToMonitor(s models.SiteConfig, alertIDToName map[int]string) Monitor {
|
||||||
m := Monitor{
|
m := Monitor{
|
||||||
Name: s.Name,
|
Name: s.Name,
|
||||||
Type: s.Type,
|
Type: s.Type,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func TestExportAlertNames(t *testing.T) {
|
|||||||
s := newTestStore(t)
|
s := newTestStore(t)
|
||||||
s.AddAlert(context.Background(), "Discord", "discord", map[string]string{"url": "https://example.com"})
|
s.AddAlert(context.Background(), "Discord", "discord", map[string]string{"url": "https://example.com"})
|
||||||
alerts, _ := s.GetAllAlerts(context.Background())
|
alerts, _ := s.GetAllAlerts(context.Background())
|
||||||
s.AddSite(context.Background(), models.Site{Name: "Web", URL: "https://example.com", Type: "http", Interval: 30, AlertID: alerts[0].ID, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
s.AddSite(context.Background(), models.SiteConfig{Name: "Web", URL: "https://example.com", Type: "http", Interval: 30, AlertID: alerts[0].ID, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
||||||
|
|
||||||
f, err := Export(context.Background(), s)
|
f, err := Export(context.Background(), s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -39,9 +39,9 @@ func TestExportAlertNames(t *testing.T) {
|
|||||||
|
|
||||||
func TestExportGroupHierarchy(t *testing.T) {
|
func TestExportGroupHierarchy(t *testing.T) {
|
||||||
s := newTestStore(t)
|
s := newTestStore(t)
|
||||||
groupID, _ := s.AddSiteReturningID(context.Background(), models.Site{Name: "Prod", Type: "group", ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
groupID, _ := s.AddSiteReturningID(context.Background(), models.SiteConfig{Name: "Prod", Type: "group", ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
||||||
s.AddSite(context.Background(), models.Site{Name: "Prod Web", URL: "https://prod.example.com", Type: "http", Interval: 15, ParentID: groupID, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
s.AddSite(context.Background(), models.SiteConfig{Name: "Prod Web", URL: "https://prod.example.com", Type: "http", Interval: 15, ParentID: groupID, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
||||||
s.AddSite(context.Background(), models.Site{Name: "Top Level", URL: "https://example.com", Type: "http", Interval: 30, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
s.AddSite(context.Background(), models.SiteConfig{Name: "Top Level", URL: "https://example.com", Type: "http", Interval: 30, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
||||||
|
|
||||||
f, err := Export(context.Background(), s)
|
f, err := Export(context.Background(), s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -72,7 +72,7 @@ func TestExportGroupHierarchy(t *testing.T) {
|
|||||||
|
|
||||||
func TestExportOmitsDefaults(t *testing.T) {
|
func TestExportOmitsDefaults(t *testing.T) {
|
||||||
s := newTestStore(t)
|
s := newTestStore(t)
|
||||||
s.AddSite(context.Background(), models.Site{
|
s.AddSite(context.Background(), models.SiteConfig{
|
||||||
Name: "Web", URL: "https://example.com", Type: "http", Interval: 30,
|
Name: "Web", URL: "https://example.com", Type: "http", Interval: 30,
|
||||||
Method: "GET", AcceptedCodes: "200-299", ExpiryThreshold: 7,
|
Method: "GET", AcceptedCodes: "200-299", ExpiryThreshold: 7,
|
||||||
})
|
})
|
||||||
@@ -98,8 +98,8 @@ func TestExportRoundTrip(t *testing.T) {
|
|||||||
s1 := newTestStore(t)
|
s1 := newTestStore(t)
|
||||||
s1.AddAlert(context.Background(), "Discord", "discord", map[string]string{"url": "https://example.com"})
|
s1.AddAlert(context.Background(), "Discord", "discord", map[string]string{"url": "https://example.com"})
|
||||||
alerts, _ := s1.GetAllAlerts(context.Background())
|
alerts, _ := s1.GetAllAlerts(context.Background())
|
||||||
s1.AddSite(context.Background(), models.Site{Name: "Web", URL: "https://example.com", Type: "http", Interval: 30, AlertID: alerts[0].ID, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
s1.AddSite(context.Background(), models.SiteConfig{Name: "Web", URL: "https://example.com", Type: "http", Interval: 30, AlertID: alerts[0].ID, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
||||||
s1.AddSite(context.Background(), models.Site{Name: "Ping", Type: "ping", Hostname: "10.0.0.1", Interval: 60, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
s1.AddSite(context.Background(), models.SiteConfig{Name: "Ping", Type: "ping", Hostname: "10.0.0.1", Interval: 60, ExpiryThreshold: 7, Method: "GET", AcceptedCodes: "200-299"})
|
||||||
|
|
||||||
exported, err := Export(context.Background(), s1)
|
exported, err := Export(context.Background(), s1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ package importer
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KumaBackup struct {
|
type KumaBackup struct {
|
||||||
@@ -80,7 +81,7 @@ func ConvertKuma(kb *KumaBackup) models.Backup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sites []models.Site
|
var sites []models.SiteConfig
|
||||||
for _, m := range kb.MonitorList {
|
for _, m := range kb.MonitorList {
|
||||||
site := convertKumaMonitor(m, kumaToUpkeepAlert)
|
site := convertKumaMonitor(m, kumaToUpkeepAlert)
|
||||||
sites = append(sites, site)
|
sites = append(sites, site)
|
||||||
@@ -132,8 +133,8 @@ func convertKumaNotifications(entries []KumaNotifEntry) map[int]models.AlertConf
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKumaMonitor(m KumaMonitor, alertMap map[int]int) models.Site {
|
func convertKumaMonitor(m KumaMonitor, alertMap map[int]int) models.SiteConfig {
|
||||||
site := models.Site{
|
site := models.SiteConfig{
|
||||||
ID: m.ID,
|
ID: m.ID,
|
||||||
Name: m.Name,
|
Name: m.Name,
|
||||||
Description: m.Description,
|
Description: m.Description,
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ import (
|
|||||||
|
|
||||||
type mockStore struct {
|
type mockStore struct {
|
||||||
storetest.BaseMock
|
storetest.BaseMock
|
||||||
sites []models.Site
|
sites []models.SiteConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockStore) GetSites(_ context.Context) ([]models.Site, error) {
|
func (m *mockStore) GetSites(_ context.Context) ([]models.SiteConfig, error) {
|
||||||
return m.sites, nil
|
return m.sites, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMetricsHandler(t *testing.T) {
|
func TestMetricsHandler(t *testing.T) {
|
||||||
ms := &mockStore{
|
ms := &mockStore{
|
||||||
sites: []models.Site{
|
sites: []models.SiteConfig{
|
||||||
{ID: 1, Name: "Example", URL: "https://example.com", Type: "http", Interval: 30},
|
{ID: 1, Name: "Example", URL: "https://example.com", Type: "http", Interval: 30},
|
||||||
{ID: 2, Name: "DNS Check", Type: "dns", Interval: 60},
|
{ID: 2, Name: "DNS Check", Type: "dns", Interval: 60},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package models
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Site struct {
|
type SiteConfig struct {
|
||||||
ID int
|
ID int
|
||||||
Name string
|
Name string
|
||||||
URL string
|
URL string
|
||||||
@@ -26,7 +26,9 @@ type Site struct {
|
|||||||
IgnoreTLS bool
|
IgnoreTLS bool
|
||||||
Paused bool
|
Paused bool
|
||||||
Regions string
|
Regions string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SiteState struct {
|
||||||
FailureCount int
|
FailureCount int
|
||||||
Status Status
|
Status Status
|
||||||
StatusCode int
|
StatusCode int
|
||||||
@@ -40,6 +42,11 @@ type Site struct {
|
|||||||
LastSuccessAt time.Time
|
LastSuccessAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Site struct {
|
||||||
|
SiteConfig
|
||||||
|
SiteState
|
||||||
|
}
|
||||||
|
|
||||||
type StateChange struct {
|
type StateChange struct {
|
||||||
ID int
|
ID int
|
||||||
SiteID int
|
SiteID int
|
||||||
@@ -103,7 +110,7 @@ type MaintenanceWindow struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Backup struct {
|
type Backup struct {
|
||||||
Sites []Site `json:"sites"`
|
Sites []SiteConfig `json:"sites"`
|
||||||
Alerts []AlertConfig `json:"alerts"`
|
Alerts []AlertConfig `json:"alerts"`
|
||||||
Users []User `json:"users"`
|
Users []User `json:"users"`
|
||||||
MaintenanceWindows []MaintenanceWindow `json:"maintenance_windows,omitempty"`
|
MaintenanceWindows []MaintenanceWindow `json:"maintenance_windows,omitempty"`
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ type CheckResult struct {
|
|||||||
ErrorReason string
|
ErrorReason string
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunCheck(ctx context.Context, site models.Site, strict, insecure *http.Client, globalInsecure bool, allowPrivate ...bool) CheckResult {
|
func RunCheck(ctx context.Context, site models.SiteConfig, strict, insecure *http.Client, globalInsecure bool, allowPrivate ...bool) CheckResult {
|
||||||
private := len(allowPrivate) > 0 && allowPrivate[0]
|
private := len(allowPrivate) > 0 && allowPrivate[0]
|
||||||
|
|
||||||
if site.Type != "http" && site.Type != "dns" && !private {
|
if site.Type != "http" && site.Type != "dns" && !private {
|
||||||
@@ -68,7 +68,7 @@ func RunCheck(ctx context.Context, site models.Site, strict, insecure *http.Clie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHTTPCheck(ctx context.Context, site models.Site, strict, insecure *http.Client, globalInsecure bool) CheckResult {
|
func runHTTPCheck(ctx context.Context, site models.SiteConfig, strict, insecure *http.Client, globalInsecure bool) CheckResult {
|
||||||
method := site.Method
|
method := site.Method
|
||||||
if method == "" {
|
if method == "" {
|
||||||
method = "GET"
|
method = "GET"
|
||||||
@@ -128,7 +128,7 @@ func runHTTPCheck(ctx context.Context, site models.Site, strict, insecure *http.
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPingCheck(_ context.Context, site models.Site) CheckResult {
|
func runPingCheck(_ context.Context, site models.SiteConfig) CheckResult {
|
||||||
host := site.Hostname
|
host := site.Hostname
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = site.URL
|
host = site.URL
|
||||||
@@ -157,7 +157,7 @@ func runPingCheck(_ context.Context, site models.Site) CheckResult {
|
|||||||
return CheckResult{SiteID: site.ID, Status: string(models.StatusUp), LatencyNs: stats.AvgRtt.Nanoseconds()}
|
return CheckResult{SiteID: site.ID, Status: string(models.StatusUp), LatencyNs: stats.AvgRtt.Nanoseconds()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPortCheck(_ context.Context, site models.Site) CheckResult {
|
func runPortCheck(_ context.Context, site models.SiteConfig) CheckResult {
|
||||||
host := site.Hostname
|
host := site.Hostname
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = site.URL
|
host = site.URL
|
||||||
@@ -176,7 +176,7 @@ func runPortCheck(_ context.Context, site models.Site) CheckResult {
|
|||||||
return CheckResult{SiteID: site.ID, Status: string(models.StatusUp), LatencyNs: latency.Nanoseconds()}
|
return CheckResult{SiteID: site.ID, Status: string(models.StatusUp), LatencyNs: latency.Nanoseconds()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDNSCheck(_ context.Context, site models.Site) CheckResult {
|
func runDNSCheck(_ context.Context, site models.SiteConfig) CheckResult {
|
||||||
host := site.Hostname
|
host := site.Hostname
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = site.URL
|
host = site.URL
|
||||||
@@ -229,7 +229,7 @@ func runDNSCheck(_ context.Context, site models.Site) CheckResult {
|
|||||||
return CheckResult{SiteID: site.ID, Status: string(models.StatusUp), LatencyNs: latency.Nanoseconds()}
|
return CheckResult{SiteID: site.ID, Status: string(models.StatusUp), LatencyNs: latency.Nanoseconds()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func siteTimeout(site models.Site) time.Duration {
|
func siteTimeout(site models.SiteConfig) time.Duration {
|
||||||
if site.Timeout > 0 {
|
if site.Timeout > 0 {
|
||||||
return time.Duration(site.Timeout) * time.Second
|
return time.Duration(site.Timeout) * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestRunCheck_HTTP_Success(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
site := models.Site{ID: 1, Type: "http", URL: srv.URL}
|
site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL}
|
||||||
result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false)
|
result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false)
|
||||||
|
|
||||||
if result.Status != "UP" {
|
if result.Status != "UP" {
|
||||||
@@ -39,7 +39,7 @@ func TestRunCheck_HTTP_ServerError(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
site := models.Site{ID: 1, Type: "http", URL: srv.URL}
|
site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL}
|
||||||
result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false)
|
result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false)
|
||||||
|
|
||||||
if result.Status != "DOWN" {
|
if result.Status != "DOWN" {
|
||||||
@@ -60,7 +60,7 @@ func TestRunCheck_HTTP_CustomAcceptedCodes(t *testing.T) {
|
|||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
}}
|
}}
|
||||||
|
|
||||||
site := models.Site{ID: 1, Type: "http", URL: srv.URL, AcceptedCodes: "200-399"}
|
site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL, AcceptedCodes: "200-399"}
|
||||||
result := RunCheck(context.Background(), site, client, client, false)
|
result := RunCheck(context.Background(), site, client, client, false)
|
||||||
|
|
||||||
if result.Status != "UP" {
|
if result.Status != "UP" {
|
||||||
@@ -76,7 +76,7 @@ func TestRunCheck_HTTP_MethodRespected(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
site := models.Site{ID: 1, Type: "http", URL: srv.URL, Method: "HEAD"}
|
site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL, Method: "HEAD"}
|
||||||
RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false)
|
RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false)
|
||||||
|
|
||||||
if receivedMethod != "HEAD" {
|
if receivedMethod != "HEAD" {
|
||||||
@@ -91,7 +91,7 @@ func TestRunCheck_HTTP_Timeout(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
site := models.Site{ID: 1, Type: "http", URL: srv.URL, Timeout: 1}
|
site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL, Timeout: 1}
|
||||||
result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false)
|
result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false)
|
||||||
|
|
||||||
if result.Status != "DOWN" {
|
if result.Status != "DOWN" {
|
||||||
@@ -109,7 +109,7 @@ func TestRunCheck_HTTP_SSLFields(t *testing.T) {
|
|||||||
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
|
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
|
||||||
}
|
}
|
||||||
|
|
||||||
site := models.Site{ID: 1, Type: "http", URL: srv.URL, CheckSSL: true, IgnoreTLS: true}
|
site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL, CheckSSL: true, IgnoreTLS: true}
|
||||||
result := RunCheck(context.Background(), site, http.DefaultClient, insecureClient, false)
|
result := RunCheck(context.Background(), site, http.DefaultClient, insecureClient, false)
|
||||||
|
|
||||||
if result.Status != "UP" {
|
if result.Status != "UP" {
|
||||||
@@ -133,7 +133,7 @@ func TestRunCheck_Port_Open(t *testing.T) {
|
|||||||
_, portStr, _ := net.SplitHostPort(ln.Addr().String())
|
_, portStr, _ := net.SplitHostPort(ln.Addr().String())
|
||||||
port, _ := strconv.Atoi(portStr)
|
port, _ := strconv.Atoi(portStr)
|
||||||
|
|
||||||
site := models.Site{ID: 1, Type: "port", Hostname: "127.0.0.1", Port: port, Timeout: 2}
|
site := models.SiteConfig{ID: 1, Type: "port", Hostname: "127.0.0.1", Port: port, Timeout: 2}
|
||||||
result := RunCheck(context.Background(), site, nil, nil, false, true)
|
result := RunCheck(context.Background(), site, nil, nil, false, true)
|
||||||
|
|
||||||
if result.Status != "UP" {
|
if result.Status != "UP" {
|
||||||
@@ -153,7 +153,7 @@ func TestRunCheck_Port_Closed(t *testing.T) {
|
|||||||
port, _ := strconv.Atoi(portStr)
|
port, _ := strconv.Atoi(portStr)
|
||||||
ln.Close()
|
ln.Close()
|
||||||
|
|
||||||
site := models.Site{ID: 1, Type: "port", Hostname: "127.0.0.1", Port: port, Timeout: 1}
|
site := models.SiteConfig{ID: 1, Type: "port", Hostname: "127.0.0.1", Port: port, Timeout: 1}
|
||||||
result := RunCheck(context.Background(), site, nil, nil, false, true)
|
result := RunCheck(context.Background(), site, nil, nil, false, true)
|
||||||
|
|
||||||
if result.Status != "DOWN" {
|
if result.Status != "DOWN" {
|
||||||
@@ -171,7 +171,7 @@ func TestRunCheck_Port_BlocksPrivateByDefault(t *testing.T) {
|
|||||||
_, portStr, _ := net.SplitHostPort(ln.Addr().String())
|
_, portStr, _ := net.SplitHostPort(ln.Addr().String())
|
||||||
port, _ := strconv.Atoi(portStr)
|
port, _ := strconv.Atoi(portStr)
|
||||||
|
|
||||||
site := models.Site{ID: 1, Type: "port", Hostname: "127.0.0.1", Port: port, Timeout: 2}
|
site := models.SiteConfig{ID: 1, Type: "port", Hostname: "127.0.0.1", Port: port, Timeout: 2}
|
||||||
result := RunCheck(context.Background(), site, nil, nil, false)
|
result := RunCheck(context.Background(), site, nil, nil, false)
|
||||||
|
|
||||||
if result.Status != "DOWN" {
|
if result.Status != "DOWN" {
|
||||||
@@ -180,7 +180,7 @@ func TestRunCheck_Port_BlocksPrivateByDefault(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunCheck_UnknownType(t *testing.T) {
|
func TestRunCheck_UnknownType(t *testing.T) {
|
||||||
site := models.Site{ID: 1, Type: "invalid"}
|
site := models.SiteConfig{ID: 1, Type: "invalid"}
|
||||||
result := RunCheck(context.Background(), site, nil, nil, false)
|
result := RunCheck(context.Background(), site, nil, nil, false)
|
||||||
|
|
||||||
if result.Status != "DOWN" {
|
if result.Status != "DOWN" {
|
||||||
@@ -214,10 +214,10 @@ func TestIsCodeAccepted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSiteTimeout(t *testing.T) {
|
func TestSiteTimeout(t *testing.T) {
|
||||||
if got := siteTimeout(models.Site{Timeout: 0}); got != 5*time.Second {
|
if got := siteTimeout(models.SiteConfig{Timeout: 0}); got != 5*time.Second {
|
||||||
t.Errorf("expected 5s default, got %v", got)
|
t.Errorf("expected 5s default, got %v", got)
|
||||||
}
|
}
|
||||||
if got := siteTimeout(models.Site{Timeout: 10}); got != 10*time.Second {
|
if got := siteTimeout(models.SiteConfig{Timeout: 10}); got != 10*time.Second {
|
||||||
t.Errorf("expected 10s, got %v", got)
|
t.Errorf("expected 10s, got %v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-29
@@ -418,7 +418,7 @@ func (e *Engine) Start(ctx context.Context) {
|
|||||||
|
|
||||||
e.refreshMaintenanceCache(ctx)
|
e.refreshMaintenanceCache(ctx)
|
||||||
|
|
||||||
sites, err := e.db.GetSites(ctx)
|
configs, err := e.db.GetSites(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.AddLog(fmt.Sprintf("Failed to load sites: %v", err))
|
e.AddLog(fmt.Sprintf("Failed to load sites: %v", err))
|
||||||
select {
|
select {
|
||||||
@@ -428,31 +428,31 @@ func (e *Engine) Start(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, s := range sites {
|
for _, cfg := range configs {
|
||||||
e.mu.RLock()
|
e.mu.RLock()
|
||||||
_, exists := e.liveState[s.ID]
|
_, exists := e.liveState[cfg.ID]
|
||||||
e.mu.RUnlock()
|
e.mu.RUnlock()
|
||||||
if !exists {
|
if !exists {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
s.Status = models.StatusPending
|
site := models.Site{SiteConfig: cfg, SiteState: models.SiteState{Status: models.StatusPending}}
|
||||||
if h, ok := e.GetHistory(s.ID); ok && len(h.Statuses) > 0 {
|
if h, ok := e.GetHistory(cfg.ID); ok && len(h.Statuses) > 0 {
|
||||||
if h.Statuses[len(h.Statuses)-1] {
|
if h.Statuses[len(h.Statuses)-1] {
|
||||||
s.Status = models.StatusUp
|
site.Status = models.StatusUp
|
||||||
} else {
|
} else {
|
||||||
s.Status = models.StatusDown
|
site.Status = models.StatusDown
|
||||||
}
|
}
|
||||||
if len(h.Latencies) > 0 {
|
if len(h.Latencies) > 0 {
|
||||||
s.Latency = h.Latencies[len(h.Latencies)-1]
|
site.Latency = h.Latencies[len(h.Latencies)-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
e.liveState[s.ID] = s
|
e.liveState[cfg.ID] = site
|
||||||
e.addToTokenIndex(s)
|
e.addToTokenIndex(site)
|
||||||
e.mu.Unlock()
|
e.mu.Unlock()
|
||||||
e.checkerWG.Add(1)
|
e.checkerWG.Add(1)
|
||||||
go func(id int) {
|
go func(id int) {
|
||||||
defer e.checkerWG.Done()
|
defer e.checkerWG.Done()
|
||||||
e.monitorRoutine(ctx, id)
|
e.monitorRoutine(ctx, id)
|
||||||
}(s.ID)
|
}(cfg.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,27 +498,17 @@ func (e *Engine) pruneMaintenanceWindows(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) UpdateSiteConfig(site models.Site) {
|
func (e *Engine) UpdateSiteConfig(cfg models.SiteConfig) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
if existing, ok := e.liveState[site.ID]; ok {
|
if existing, ok := e.liveState[cfg.ID]; ok {
|
||||||
e.removeFromTokenIndex(site.ID)
|
e.removeFromTokenIndex(cfg.ID)
|
||||||
site.Status = existing.Status
|
existing.SiteConfig = cfg
|
||||||
site.StatusCode = existing.StatusCode
|
e.liveState[cfg.ID] = existing
|
||||||
site.Latency = existing.Latency
|
e.addToTokenIndex(existing)
|
||||||
site.CertExpiry = existing.CertExpiry
|
|
||||||
site.HasSSL = existing.HasSSL
|
|
||||||
site.LastCheck = existing.LastCheck
|
|
||||||
site.SentSSLWarning = existing.SentSSLWarning
|
|
||||||
site.FailureCount = existing.FailureCount
|
|
||||||
site.LastError = existing.LastError
|
|
||||||
site.StatusChangedAt = existing.StatusChangedAt
|
|
||||||
site.LastSuccessAt = existing.LastSuccessAt
|
|
||||||
e.liveState[site.ID] = site
|
|
||||||
e.addToTokenIndex(site)
|
|
||||||
}
|
}
|
||||||
e.mu.Unlock()
|
e.mu.Unlock()
|
||||||
|
|
||||||
e.signalRecheck(site.ID)
|
e.signalRecheck(cfg.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) getRecheckChan(id int) chan struct{} {
|
func (e *Engine) getRecheckChan(id int) chan struct{} {
|
||||||
@@ -675,7 +665,7 @@ func (e *Engine) checkByID(ctx context.Context, id int) {
|
|||||||
case "group":
|
case "group":
|
||||||
e.checkGroup(ctx, site)
|
e.checkGroup(ctx, site)
|
||||||
default:
|
default:
|
||||||
result := RunCheck(ctx, site, e.strictClient, e.insecureClient, e.insecureSkipVerify, e.allowPrivateTargets)
|
result := RunCheck(ctx, site.SiteConfig, e.strictClient, e.insecureClient, e.insecureSkipVerify, e.allowPrivateTargets)
|
||||||
updatedSite := site
|
updatedSite := site
|
||||||
updatedSite.HasSSL = result.HasSSL
|
updatedSite.HasSSL = result.HasSSL
|
||||||
updatedSite.CertExpiry = result.CertExpiry
|
updatedSite.CertExpiry = result.CertExpiry
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type savedCheck struct {
|
|||||||
type mockStore struct {
|
type mockStore struct {
|
||||||
storetest.BaseMock
|
storetest.BaseMock
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
sites []models.Site
|
sites []models.SiteConfig
|
||||||
alerts map[int]models.AlertConfig
|
alerts map[int]models.AlertConfig
|
||||||
maintenance map[int]bool
|
maintenance map[int]bool
|
||||||
logs []string
|
logs []string
|
||||||
@@ -40,7 +40,7 @@ func newMockStore() *mockStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockStore) GetSites(context.Context) ([]models.Site, error) { return m.sites, nil }
|
func (m *mockStore) GetSites(context.Context) ([]models.SiteConfig, error) { return m.sites, nil }
|
||||||
|
|
||||||
func (m *mockStore) GetActiveMaintenanceWindows(context.Context) ([]models.MaintenanceWindow, error) {
|
func (m *mockStore) GetActiveMaintenanceWindows(context.Context) ([]models.MaintenanceWindow, error) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
@@ -148,7 +148,10 @@ func (m *mockStore) getAlertCallsSnapshot() []int {
|
|||||||
func TestHandleStatusChange_PendingToUp(t *testing.T) {
|
func TestHandleStatusChange_PendingToUp(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "PENDING", MaxRetries: 3, AlertID: 1}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 3, AlertID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "PENDING"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.handleStatusChange(site, "UP", 200, 10*time.Millisecond, "")
|
e.handleStatusChange(site, "UP", 200, 10*time.Millisecond, "")
|
||||||
@@ -169,7 +172,10 @@ func TestHandleStatusChange_PendingToUp(t *testing.T) {
|
|||||||
func TestHandleStatusChange_UpIncrementFailure(t *testing.T) {
|
func TestHandleStatusChange_UpIncrementFailure(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP", MaxRetries: 3, FailureCount: 0}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 3},
|
||||||
|
SiteState: models.SiteState{Status: "UP", FailureCount: 0},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.handleStatusChange(site, "DOWN", 500, 0, "test error")
|
e.handleStatusChange(site, "DOWN", 500, 0, "test error")
|
||||||
@@ -187,7 +193,10 @@ func TestHandleStatusChange_UpToDown_ExceedsRetries(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "discord", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "discord", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP", MaxRetries: 2, FailureCount: 2, AlertID: 1}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 2, AlertID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "UP", FailureCount: 2},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.handleStatusChange(site, "DOWN", 500, 0, "test error")
|
e.handleStatusChange(site, "DOWN", 500, 0, "test error")
|
||||||
@@ -210,7 +219,10 @@ func TestHandleStatusChange_UpToDown_ZeroRetries(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP", MaxRetries: 0, FailureCount: 0, AlertID: 1}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 0, AlertID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "UP", FailureCount: 0},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.handleStatusChange(site, "DOWN", 0, 0, "test error")
|
e.handleStatusChange(site, "DOWN", 0, 0, "test error")
|
||||||
@@ -229,7 +241,10 @@ func TestHandleStatusChange_DownToUp_Recovery(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "DOWN", FailureCount: 4, AlertID: 1}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", AlertID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "DOWN", FailureCount: 4},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.handleStatusChange(site, "UP", 200, 5*time.Millisecond, "")
|
e.handleStatusChange(site, "UP", 200, 5*time.Millisecond, "")
|
||||||
@@ -250,7 +265,10 @@ func TestHandleStatusChange_DownToUp_Recovery(t *testing.T) {
|
|||||||
func TestHandleStatusChange_DownStaysDown(t *testing.T) {
|
func TestHandleStatusChange_DownStaysDown(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "DOWN", MaxRetries: 2, FailureCount: 3}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 2},
|
||||||
|
SiteState: models.SiteState{Status: "DOWN", FailureCount: 3},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.handleStatusChange(site, "DOWN", 0, 0, "test error")
|
e.handleStatusChange(site, "DOWN", 0, 0, "test error")
|
||||||
@@ -269,7 +287,10 @@ func TestHandleStatusChange_SSLExpired(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP", MaxRetries: 0, AlertID: 1}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 0, AlertID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.handleStatusChange(site, "SSL EXP", 0, 0, "SSL certificate expired")
|
e.handleStatusChange(site, "SSL EXP", 0, 0, "SSL certificate expired")
|
||||||
@@ -289,7 +310,10 @@ func TestHandleStatusChange_AlertSuppressedMaintenance(t *testing.T) {
|
|||||||
ms.maintenance[1] = true
|
ms.maintenance[1] = true
|
||||||
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP", MaxRetries: 0, AlertID: 1}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 0, AlertID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
e.refreshMaintenanceCache(context.Background())
|
e.refreshMaintenanceCache(context.Background())
|
||||||
|
|
||||||
@@ -321,7 +345,10 @@ func TestHandleStatusChange_RecoverySuppressedMaintenance(t *testing.T) {
|
|||||||
ms.maintenance[1] = true
|
ms.maintenance[1] = true
|
||||||
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "DOWN", AlertID: 1}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", AlertID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "DOWN"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
e.refreshMaintenanceCache(context.Background())
|
e.refreshMaintenanceCache(context.Background())
|
||||||
|
|
||||||
@@ -342,10 +369,8 @@ func TestHandleStatusChange_SSLWarning(t *testing.T) {
|
|||||||
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{
|
site := models.Site{
|
||||||
ID: 1, Name: "test", Status: "UP", Type: "http",
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", Type: "http", CheckSSL: true, ExpiryThreshold: 30, AlertID: 1},
|
||||||
CheckSSL: true, HasSSL: true, ExpiryThreshold: 30,
|
SiteState: models.SiteState{Status: "UP", HasSSL: true, SentSSLWarning: false, CertExpiry: time.Now().Add(15 * 24 * time.Hour)},
|
||||||
SentSSLWarning: false, AlertID: 1,
|
|
||||||
CertExpiry: time.Now().Add(15 * 24 * time.Hour),
|
|
||||||
}
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
@@ -365,10 +390,8 @@ func TestHandleStatusChange_SSLWarningNotRepeated(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{
|
site := models.Site{
|
||||||
ID: 1, Name: "test", Status: "UP", Type: "http",
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", Type: "http", CheckSSL: true, ExpiryThreshold: 30, AlertID: 1},
|
||||||
CheckSSL: true, HasSSL: true, ExpiryThreshold: 30,
|
SiteState: models.SiteState{Status: "UP", HasSSL: true, SentSSLWarning: true, CertExpiry: time.Now().Add(15 * 24 * time.Hour)},
|
||||||
SentSSLWarning: true, AlertID: 1,
|
|
||||||
CertExpiry: time.Now().Add(15 * 24 * time.Hour),
|
|
||||||
}
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
@@ -384,10 +407,8 @@ func TestHandleStatusChange_SSLWarningReset(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{
|
site := models.Site{
|
||||||
ID: 1, Name: "test", Status: "UP", Type: "http",
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", Type: "http", CheckSSL: true, ExpiryThreshold: 30},
|
||||||
CheckSSL: true, HasSSL: true, ExpiryThreshold: 30,
|
SiteState: models.SiteState{Status: "UP", HasSSL: true, SentSSLWarning: true, CertExpiry: time.Now().Add(60 * 24 * time.Hour)},
|
||||||
SentSSLWarning: true,
|
|
||||||
CertExpiry: time.Now().Add(60 * 24 * time.Hour),
|
|
||||||
}
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
@@ -405,10 +426,8 @@ func TestHandleStatusChange_SSLWarningSuppressedMaint(t *testing.T) {
|
|||||||
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{
|
site := models.Site{
|
||||||
ID: 1, Name: "test", Status: "UP", Type: "http",
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", Type: "http", CheckSSL: true, ExpiryThreshold: 30, AlertID: 1},
|
||||||
CheckSSL: true, HasSSL: true, ExpiryThreshold: 30,
|
SiteState: models.SiteState{Status: "UP", HasSSL: true, SentSSLWarning: false, CertExpiry: time.Now().Add(15 * 24 * time.Hour)},
|
||||||
SentSSLWarning: false, AlertID: 1,
|
|
||||||
CertExpiry: time.Now().Add(15 * 24 * time.Hour),
|
|
||||||
}
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
e.refreshMaintenanceCache(context.Background())
|
e.refreshMaintenanceCache(context.Background())
|
||||||
@@ -428,7 +447,10 @@ func TestHandleStatusChange_SSLWarningSuppressedMaint(t *testing.T) {
|
|||||||
func TestHandleStatusChange_InactiveEngine(t *testing.T) {
|
func TestHandleStatusChange_InactiveEngine(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP", MaxRetries: 0}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 0},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
e.SetActive(false)
|
e.SetActive(false)
|
||||||
|
|
||||||
@@ -445,7 +467,10 @@ func TestHandleStatusChange_InactiveEngine(t *testing.T) {
|
|||||||
func TestRecordHeartbeat_ValidToken(t *testing.T) {
|
func TestRecordHeartbeat_ValidToken(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "push-test", Type: "push", Token: "abc123", Status: "UP"}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "push-test", Type: "push", Token: "abc123"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
if !e.RecordHeartbeat("abc123") {
|
if !e.RecordHeartbeat("abc123") {
|
||||||
@@ -465,7 +490,10 @@ func TestRecordHeartbeat_RecoveryFromDown(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
ms.alerts[1] = models.AlertConfig{ID: 1, Name: "test", Type: "webhook", Settings: map[string]string{"url": "http://example.com"}}
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "push-test", Type: "push", Token: "abc123", Status: "DOWN", AlertID: 1, FailureCount: 3}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "push-test", Type: "push", Token: "abc123", AlertID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "DOWN", FailureCount: 3},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
if !e.RecordHeartbeat("abc123") {
|
if !e.RecordHeartbeat("abc123") {
|
||||||
@@ -497,7 +525,10 @@ func TestRecordHeartbeat_UnknownToken(t *testing.T) {
|
|||||||
func TestRecordHeartbeat_InactiveEngine(t *testing.T) {
|
func TestRecordHeartbeat_InactiveEngine(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Type: "push", Token: "abc123", Status: "UP"}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Type: "push", Token: "abc123"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
e.SetActive(false)
|
e.SetActive(false)
|
||||||
|
|
||||||
@@ -512,9 +543,8 @@ func TestCheckPush_DeadlineMissed(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{
|
site := models.Site{
|
||||||
ID: 1, Name: "push", Type: "push", Status: "UP",
|
SiteConfig: models.SiteConfig{ID: 1, Name: "push", Type: "push", Interval: 10, MaxRetries: 0},
|
||||||
Interval: 10, MaxRetries: 0,
|
SiteState: models.SiteState{Status: "UP", LastCheck: time.Now().Add(-120 * time.Second)},
|
||||||
LastCheck: time.Now().Add(-120 * time.Second),
|
|
||||||
}
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
@@ -530,9 +560,8 @@ func TestCheckPush_OverdueBecomesLate(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{
|
site := models.Site{
|
||||||
ID: 1, Name: "push", Type: "push", Status: "UP",
|
SiteConfig: models.SiteConfig{ID: 1, Name: "push", Type: "push", Interval: 300},
|
||||||
Interval: 300,
|
SiteState: models.SiteState{Status: "UP", LastCheck: time.Now().Add(-310 * time.Second)},
|
||||||
LastCheck: time.Now().Add(-310 * time.Second),
|
|
||||||
}
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
@@ -550,9 +579,8 @@ func TestCheckPush_OverdueBecomesStale(t *testing.T) {
|
|||||||
// interval=300, grace=150 (300/2), staleMark=overdue+75
|
// interval=300, grace=150 (300/2), staleMark=overdue+75
|
||||||
// at 380s: past staleMark(375) but before graceEnd(450)
|
// at 380s: past staleMark(375) but before graceEnd(450)
|
||||||
site := models.Site{
|
site := models.Site{
|
||||||
ID: 1, Name: "push", Type: "push", Status: "UP",
|
SiteConfig: models.SiteConfig{ID: 1, Name: "push", Type: "push", Interval: 300},
|
||||||
Interval: 300,
|
SiteState: models.SiteState{Status: "UP", LastCheck: time.Now().Add(-380 * time.Second)},
|
||||||
LastCheck: time.Now().Add(-380 * time.Second),
|
|
||||||
}
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
@@ -568,8 +596,8 @@ func TestCheckPush_WithinDeadline(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{
|
site := models.Site{
|
||||||
ID: 1, Name: "push", Type: "push", Status: "UP",
|
SiteConfig: models.SiteConfig{ID: 1, Name: "push", Type: "push", Interval: 60},
|
||||||
Interval: 60, LastCheck: time.Now(),
|
SiteState: models.SiteState{Status: "UP", LastCheck: time.Now()},
|
||||||
}
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
@@ -585,8 +613,8 @@ func TestCheckPush_PendingStaysPending(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{
|
site := models.Site{
|
||||||
ID: 1, Name: "push", Type: "push", Status: "PENDING",
|
SiteConfig: models.SiteConfig{ID: 1, Name: "push", Type: "push", Interval: 60},
|
||||||
Interval: 60,
|
SiteState: models.SiteState{Status: "PENDING"},
|
||||||
}
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
@@ -603,9 +631,18 @@ func TestCheckPush_PendingStaysPending(t *testing.T) {
|
|||||||
func TestCheckGroup_AllChildrenUp(t *testing.T) {
|
func TestCheckGroup_AllChildrenUp(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
group := models.Site{ID: 1, Name: "group", Type: "group", Status: "PENDING"}
|
group := models.Site{
|
||||||
child1 := models.Site{ID: 2, Name: "child1", Type: "http", ParentID: 1, Status: "UP"}
|
SiteConfig: models.SiteConfig{ID: 1, Name: "group", Type: "group"},
|
||||||
child2 := models.Site{ID: 3, Name: "child2", Type: "http", ParentID: 1, Status: "UP"}
|
SiteState: models.SiteState{Status: "PENDING"},
|
||||||
|
}
|
||||||
|
child1 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 2, Name: "child1", Type: "http", ParentID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
|
child2 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 3, Name: "child2", Type: "http", ParentID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, group)
|
injectSite(e, group)
|
||||||
injectSite(e, child1)
|
injectSite(e, child1)
|
||||||
injectSite(e, child2)
|
injectSite(e, child2)
|
||||||
@@ -621,9 +658,18 @@ func TestCheckGroup_AllChildrenUp(t *testing.T) {
|
|||||||
func TestCheckGroup_OneChildDown(t *testing.T) {
|
func TestCheckGroup_OneChildDown(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
group := models.Site{ID: 1, Name: "group", Type: "group", Status: "UP"}
|
group := models.Site{
|
||||||
child1 := models.Site{ID: 2, Name: "child1", Type: "http", ParentID: 1, Status: "UP"}
|
SiteConfig: models.SiteConfig{ID: 1, Name: "group", Type: "group"},
|
||||||
child2 := models.Site{ID: 3, Name: "child2", Type: "http", ParentID: 1, Status: "DOWN"}
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
|
child1 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 2, Name: "child1", Type: "http", ParentID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
|
child2 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 3, Name: "child2", Type: "http", ParentID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "DOWN"},
|
||||||
|
}
|
||||||
injectSite(e, group)
|
injectSite(e, group)
|
||||||
injectSite(e, child1)
|
injectSite(e, child1)
|
||||||
injectSite(e, child2)
|
injectSite(e, child2)
|
||||||
@@ -639,9 +685,17 @@ func TestCheckGroup_OneChildDown(t *testing.T) {
|
|||||||
func TestCheckGroup_PausedChildIgnored(t *testing.T) {
|
func TestCheckGroup_PausedChildIgnored(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
group := models.Site{ID: 1, Name: "group", Type: "group"}
|
group := models.Site{
|
||||||
child1 := models.Site{ID: 2, Name: "child1", Type: "http", ParentID: 1, Status: "UP"}
|
SiteConfig: models.SiteConfig{ID: 1, Name: "group", Type: "group"},
|
||||||
child2 := models.Site{ID: 3, Name: "child2", Type: "http", ParentID: 1, Status: "DOWN", Paused: true}
|
}
|
||||||
|
child1 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 2, Name: "child1", Type: "http", ParentID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
|
child2 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 3, Name: "child2", Type: "http", ParentID: 1, Paused: true},
|
||||||
|
SiteState: models.SiteState{Status: "DOWN"},
|
||||||
|
}
|
||||||
injectSite(e, group)
|
injectSite(e, group)
|
||||||
injectSite(e, child1)
|
injectSite(e, child1)
|
||||||
injectSite(e, child2)
|
injectSite(e, child2)
|
||||||
@@ -658,9 +712,17 @@ func TestCheckGroup_MaintenanceChildIgnored(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
ms.maintenance[3] = true
|
ms.maintenance[3] = true
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
group := models.Site{ID: 1, Name: "group", Type: "group"}
|
group := models.Site{
|
||||||
child1 := models.Site{ID: 2, Name: "child1", Type: "http", ParentID: 1, Status: "UP"}
|
SiteConfig: models.SiteConfig{ID: 1, Name: "group", Type: "group"},
|
||||||
child2 := models.Site{ID: 3, Name: "child2", Type: "http", ParentID: 1, Status: "DOWN"}
|
}
|
||||||
|
child1 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 2, Name: "child1", Type: "http", ParentID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
|
child2 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 3, Name: "child2", Type: "http", ParentID: 1},
|
||||||
|
SiteState: models.SiteState{Status: "DOWN"},
|
||||||
|
}
|
||||||
injectSite(e, group)
|
injectSite(e, group)
|
||||||
injectSite(e, child1)
|
injectSite(e, child1)
|
||||||
injectSite(e, child2)
|
injectSite(e, child2)
|
||||||
@@ -677,7 +739,10 @@ func TestCheckGroup_MaintenanceChildIgnored(t *testing.T) {
|
|||||||
func TestCheckGroup_NoChildren(t *testing.T) {
|
func TestCheckGroup_NoChildren(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
group := models.Site{ID: 1, Name: "group", Type: "group", Status: "UP"}
|
group := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "group", Type: "group"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, group)
|
injectSite(e, group)
|
||||||
|
|
||||||
e.checkGroup(context.Background(), group)
|
e.checkGroup(context.Background(), group)
|
||||||
@@ -772,10 +837,13 @@ func TestInitHistory_LoadsFromDB(t *testing.T) {
|
|||||||
func TestUpdateSiteConfig_PreservesRuntime(t *testing.T) {
|
func TestUpdateSiteConfig_PreservesRuntime(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", URL: "http://old.com", Status: "DOWN", FailureCount: 3, Latency: 100 * time.Millisecond}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", URL: "http://old.com"},
|
||||||
|
SiteState: models.SiteState{Status: "DOWN", FailureCount: 3, Latency: 100 * time.Millisecond},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
updated := models.Site{ID: 1, Name: "test", URL: "http://new.com", Interval: 60}
|
updated := models.SiteConfig{ID: 1, Name: "test", URL: "http://new.com", Interval: 60}
|
||||||
e.UpdateSiteConfig(updated)
|
e.UpdateSiteConfig(updated)
|
||||||
|
|
||||||
s, _ := getSite(e, 1)
|
s, _ := getSite(e, 1)
|
||||||
@@ -796,7 +864,10 @@ func TestUpdateSiteConfig_PreservesRuntime(t *testing.T) {
|
|||||||
func TestRemoveSite_CleansUp(t *testing.T) {
|
func TestRemoveSite_CleansUp(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Type: "push", Token: "tok1", Status: "UP"}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", Type: "push", Token: "tok1"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
e.recordCheck(1, 5*time.Millisecond, true)
|
e.recordCheck(1, 5*time.Millisecond, true)
|
||||||
|
|
||||||
@@ -816,7 +887,10 @@ func TestRemoveSite_CleansUp(t *testing.T) {
|
|||||||
func TestToggleSitePause(t *testing.T) {
|
func TestToggleSitePause(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP"}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
paused := e.ToggleSitePause(1)
|
paused := e.ToggleSitePause(1)
|
||||||
@@ -845,8 +919,14 @@ func TestToggleSitePause_NonexistentSite(t *testing.T) {
|
|||||||
func TestGetAllSites_ReturnsCopy(t *testing.T) {
|
func TestGetAllSites_ReturnsCopy(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
injectSite(e, models.Site{ID: 1, Name: "s1", Status: "UP"})
|
injectSite(e, models.Site{
|
||||||
injectSite(e, models.Site{ID: 2, Name: "s2", Status: "DOWN"})
|
SiteConfig: models.SiteConfig{ID: 1, Name: "s1"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
})
|
||||||
|
injectSite(e, models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 2, Name: "s2"},
|
||||||
|
SiteState: models.SiteState{Status: "DOWN"},
|
||||||
|
})
|
||||||
|
|
||||||
sites := e.GetAllSites()
|
sites := e.GetAllSites()
|
||||||
if len(sites) != 2 {
|
if len(sites) != 2 {
|
||||||
@@ -865,10 +945,13 @@ func TestGetAllSites_ReturnsCopy(t *testing.T) {
|
|||||||
func TestGetLiveState_ReturnsCopy(t *testing.T) {
|
func TestGetLiveState_ReturnsCopy(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
injectSite(e, models.Site{ID: 1, Name: "s1", Status: "UP"})
|
injectSite(e, models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "s1"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
})
|
||||||
|
|
||||||
state := e.GetLiveState()
|
state := e.GetLiveState()
|
||||||
state[1] = models.Site{Name: "mutated"}
|
state[1] = models.Site{SiteConfig: models.SiteConfig{Name: "mutated"}}
|
||||||
|
|
||||||
fresh := e.GetLiveState()
|
fresh := e.GetLiveState()
|
||||||
if fresh[1].Name == "mutated" {
|
if fresh[1].Name == "mutated" {
|
||||||
@@ -984,7 +1067,8 @@ func TestConcurrent_RecordHeartbeat(t *testing.T) {
|
|||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
injectSite(e, models.Site{
|
injectSite(e, models.Site{
|
||||||
ID: i + 1, Type: "push", Token: fmt.Sprintf("tok-%d", i+1), Status: "UP",
|
SiteConfig: models.SiteConfig{ID: i + 1, Type: "push", Token: fmt.Sprintf("tok-%d", i+1)},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1002,7 +1086,10 @@ func TestConcurrent_RecordHeartbeat(t *testing.T) {
|
|||||||
func TestConcurrent_HandleStatusChangeAndGetState(t *testing.T) {
|
func TestConcurrent_HandleStatusChangeAndGetState(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP", MaxRetries: 100}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 100},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -1055,7 +1142,10 @@ func TestConcurrent_RecordCheckAndGetHistory(t *testing.T) {
|
|||||||
func TestHandleStatusChange_PauseDuringCheckSurvives(t *testing.T) {
|
func TestHandleStatusChange_PauseDuringCheckSurvives(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP", MaxRetries: 0}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 0},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
// `site` is the stale snapshot the check ran against (Paused=false).
|
// `site` is the stale snapshot the check ran against (Paused=false).
|
||||||
@@ -1079,11 +1169,14 @@ func TestHandleStatusChange_PauseDuringCheckSurvives(t *testing.T) {
|
|||||||
func TestHandleStatusChange_ConfigEditDuringCheckSurvives(t *testing.T) {
|
func TestHandleStatusChange_ConfigEditDuringCheckSurvives(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", URL: "http://old.com", Type: "http", Status: "UP", MaxRetries: 0, Interval: 30}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", URL: "http://old.com", Type: "http", MaxRetries: 0, Interval: 30},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
// Config changes mid-check.
|
// Config changes mid-check.
|
||||||
e.UpdateSiteConfig(models.Site{ID: 1, Name: "test", URL: "http://new.com", Type: "http", Interval: 60})
|
e.UpdateSiteConfig(models.SiteConfig{ID: 1, Name: "test", URL: "http://new.com", Type: "http", Interval: 60})
|
||||||
|
|
||||||
// Stale check (ran against http://old.com) folds its result in.
|
// Stale check (ran against http://old.com) folds its result in.
|
||||||
e.handleStatusChange(site, "UP", 200, 5*time.Millisecond, "")
|
e.handleStatusChange(site, "UP", 200, 5*time.Millisecond, "")
|
||||||
@@ -1105,7 +1198,10 @@ func TestHandleStatusChange_HeartbeatNotOverwrittenByStaleDown(t *testing.T) {
|
|||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
// Snapshot the engine would have taken before evaluating staleness:
|
// Snapshot the engine would have taken before evaluating staleness:
|
||||||
// LastCheck is old, so checkPush decided "DOWN".
|
// LastCheck is old, so checkPush decided "DOWN".
|
||||||
snap := models.Site{ID: 1, Name: "push", Type: "push", Token: "tok", Status: "UP", Interval: 10, LastCheck: time.Now().Add(-120 * time.Second)}
|
snap := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "push", Type: "push", Token: "tok", Interval: 10},
|
||||||
|
SiteState: models.SiteState{Status: "UP", LastCheck: time.Now().Add(-120 * time.Second)},
|
||||||
|
}
|
||||||
injectSite(e, snap)
|
injectSite(e, snap)
|
||||||
|
|
||||||
// A heartbeat lands first, advancing LastCheck and confirming UP.
|
// A heartbeat lands first, advancing LastCheck and confirming UP.
|
||||||
@@ -1126,7 +1222,10 @@ func TestHandleStatusChange_HeartbeatNotOverwrittenByStaleDown(t *testing.T) {
|
|||||||
func TestHandleStatusChange_RemovedSiteDropped(t *testing.T) {
|
func TestHandleStatusChange_RemovedSiteDropped(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Status: "UP", MaxRetries: 0}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", MaxRetries: 0},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.RemoveSite(1)
|
e.RemoveSite(1)
|
||||||
@@ -1189,9 +1288,18 @@ func TestEngineStop_Idempotent(t *testing.T) {
|
|||||||
func TestCheckGroup_AllPausedNoAutoFreeze(t *testing.T) {
|
func TestCheckGroup_AllPausedNoAutoFreeze(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
group := models.Site{ID: 1, Name: "group", Type: "group", Status: "UP"}
|
group := models.Site{
|
||||||
child1 := models.Site{ID: 2, Name: "child1", Type: "http", ParentID: 1, Status: "UP", Paused: true}
|
SiteConfig: models.SiteConfig{ID: 1, Name: "group", Type: "group"},
|
||||||
child2 := models.Site{ID: 3, Name: "child2", Type: "http", ParentID: 1, Status: "UP", Paused: true}
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
|
child1 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 2, Name: "child1", Type: "http", ParentID: 1, Paused: true},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
|
child2 := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 3, Name: "child2", Type: "http", ParentID: 1, Paused: true},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, group)
|
injectSite(e, group)
|
||||||
injectSite(e, child1)
|
injectSite(e, child1)
|
||||||
injectSite(e, child2)
|
injectSite(e, child2)
|
||||||
@@ -1208,7 +1316,10 @@ func TestCheckGroup_AllPausedNoAutoFreeze(t *testing.T) {
|
|||||||
func TestHandleStatusChange_PendingRetriesBeforeDown(t *testing.T) {
|
func TestHandleStatusChange_PendingRetriesBeforeDown(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "new-monitor", Status: "PENDING", MaxRetries: 2}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "new-monitor", MaxRetries: 2},
|
||||||
|
SiteState: models.SiteState{Status: "PENDING"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.handleStatusChange(site, "DOWN", 0, 0, "timeout")
|
e.handleStatusChange(site, "DOWN", 0, 0, "timeout")
|
||||||
@@ -1237,7 +1348,10 @@ func TestHandleStatusChange_PendingRetriesBeforeDown(t *testing.T) {
|
|||||||
func TestHandleStatusChange_LateRetriesBeforeDown(t *testing.T) {
|
func TestHandleStatusChange_LateRetriesBeforeDown(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "push-mon", Status: "LATE", MaxRetries: 1}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "push-mon", MaxRetries: 1},
|
||||||
|
SiteState: models.SiteState{Status: "LATE"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.handleStatusChange(site, "DOWN", 0, 0, "missed heartbeat")
|
e.handleStatusChange(site, "DOWN", 0, 0, "missed heartbeat")
|
||||||
@@ -1257,7 +1371,10 @@ func TestHandleStatusChange_LateRetriesBeforeDown(t *testing.T) {
|
|||||||
func TestIngestProbeResult_ExpiresStaleProbes(t *testing.T) {
|
func TestIngestProbeResult_ExpiresStaleProbes(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Type: "http", Status: "UP", Interval: 30}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", Type: "http", Interval: 30},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.probeResultsMu.Lock()
|
e.probeResultsMu.Lock()
|
||||||
@@ -1289,7 +1406,10 @@ func TestIngestProbeResult_ExpiresStaleProbes(t *testing.T) {
|
|||||||
func TestRemoveSite_CleansProbeResults(t *testing.T) {
|
func TestRemoveSite_CleansProbeResults(t *testing.T) {
|
||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Type: "http", Status: "UP"}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", Type: "http"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
|
|
||||||
e.probeResultsMu.Lock()
|
e.probeResultsMu.Lock()
|
||||||
@@ -1312,8 +1432,14 @@ func TestIsInMaintenance_UsesCache(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
ms.maintenance[10] = true // direct maintenance on group
|
ms.maintenance[10] = true // direct maintenance on group
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
group := models.Site{ID: 10, Name: "group", Type: "group", Status: "UP"}
|
group := models.Site{
|
||||||
child := models.Site{ID: 20, Name: "child", Type: "http", ParentID: 10, Status: "UP"}
|
SiteConfig: models.SiteConfig{ID: 10, Name: "group", Type: "group"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
|
child := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 20, Name: "child", Type: "http", ParentID: 10},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, group)
|
injectSite(e, group)
|
||||||
injectSite(e, child)
|
injectSite(e, child)
|
||||||
e.refreshMaintenanceCache(context.Background())
|
e.refreshMaintenanceCache(context.Background())
|
||||||
@@ -1334,7 +1460,10 @@ func TestIsInMaintenance_GlobalMaintenance(t *testing.T) {
|
|||||||
ms := newMockStore()
|
ms := newMockStore()
|
||||||
ms.maintenance[0] = true
|
ms.maintenance[0] = true
|
||||||
e := newTestEngine(ms)
|
e := newTestEngine(ms)
|
||||||
site := models.Site{ID: 1, Name: "test", Type: "http", Status: "UP"}
|
site := models.Site{
|
||||||
|
SiteConfig: models.SiteConfig{ID: 1, Name: "test", Type: "http"},
|
||||||
|
SiteState: models.SiteState{Status: "UP"},
|
||||||
|
}
|
||||||
injectSite(e, site)
|
injectSite(e, site)
|
||||||
e.refreshMaintenanceCache(context.Background())
|
e.refreshMaintenanceCache(context.Background())
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
type mockStore struct {
|
type mockStore struct {
|
||||||
storetest.BaseMock
|
storetest.BaseMock
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
sites []models.Site
|
sites []models.SiteConfig
|
||||||
alerts []models.AlertConfig
|
alerts []models.AlertConfig
|
||||||
nodes map[string]models.ProbeNode
|
nodes map[string]models.ProbeNode
|
||||||
importedData *models.Backup
|
importedData *models.Backup
|
||||||
@@ -35,7 +35,7 @@ func newMockStore() *mockStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockStore) GetSites(_ context.Context) ([]models.Site, error) { return m.sites, nil }
|
func (m *mockStore) GetSites(_ context.Context) ([]models.SiteConfig, error) { return m.sites, nil }
|
||||||
func (m *mockStore) GetAllAlerts(_ context.Context) ([]models.AlertConfig, error) {
|
func (m *mockStore) GetAllAlerts(_ context.Context) ([]models.AlertConfig, error) {
|
||||||
return m.alerts, nil
|
return m.alerts, nil
|
||||||
}
|
}
|
||||||
@@ -252,7 +252,7 @@ func TestExport_Unauthorized_WrongKey(t *testing.T) {
|
|||||||
|
|
||||||
func TestExport_Success(t *testing.T) {
|
func TestExport_Success(t *testing.T) {
|
||||||
ts := newTestServer(t, "secret", false)
|
ts := newTestServer(t, "secret", false)
|
||||||
ts.store.sites = []models.Site{{ID: 1, Name: "example", URL: "http://example.com"}}
|
ts.store.sites = []models.SiteConfig{{ID: 1, Name: "example", URL: "http://example.com"}}
|
||||||
|
|
||||||
resp, err := authReq("GET", ts.baseURL+"/api/backup/export", "secret", nil)
|
resp, err := authReq("GET", ts.baseURL+"/api/backup/export", "secret", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -299,7 +299,7 @@ func TestImport_Unauthorized(t *testing.T) {
|
|||||||
func TestImport_Success(t *testing.T) {
|
func TestImport_Success(t *testing.T) {
|
||||||
ts := newTestServer(t, "secret", false)
|
ts := newTestServer(t, "secret", false)
|
||||||
backup := models.Backup{
|
backup := models.Backup{
|
||||||
Sites: []models.Site{{Name: "imported", URL: "http://example.com"}},
|
Sites: []models.SiteConfig{{Name: "imported", URL: "http://example.com"}},
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(backup)
|
body, _ := json.Marshal(backup)
|
||||||
resp, err := authReq("POST", ts.baseURL+"/api/backup/import", "secret", body)
|
resp, err := authReq("POST", ts.baseURL+"/api/backup/import", "secret", body)
|
||||||
@@ -437,9 +437,9 @@ func TestStatusJSON_PublicDTOOnly(t *testing.T) {
|
|||||||
// take. The old version of this test injected via UpdateSiteConfig, which
|
// take. The old version of this test injected via UpdateSiteConfig, which
|
||||||
// no-ops for unknown IDs, so it asserted over zero sites and passed
|
// no-ops for unknown IDs, so it asserted over zero sites and passed
|
||||||
// against a server that leaked tokens.
|
// against a server that leaked tokens.
|
||||||
ts.store.sites = []models.Site{{
|
ts.store.sites = []models.SiteConfig{{
|
||||||
ID: 1, Name: "test", Type: "push", Token: "secret-token",
|
ID: 1, Name: "test", Type: "push", Token: "secret-token",
|
||||||
Hostname: "internal-host", LastError: "internal failure detail", AlertID: 3,
|
Hostname: "internal-host", AlertID: 3,
|
||||||
}}
|
}}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
ts.engine.Start(ctx)
|
ts.engine.Start(ctx)
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func (s *SQLStore) Init(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLStore) GetSites(ctx context.Context) ([]models.Site, error) {
|
func (s *SQLStore) GetSites(ctx context.Context) ([]models.SiteConfig, error) {
|
||||||
bf := s.dialect.BoolFalse()
|
bf := s.dialect.BoolFalse()
|
||||||
query := fmt.Sprintf( //nolint:gosec // bf is a dialect boolean literal, not user input
|
query := fmt.Sprintf( //nolint:gosec // bf is a dialect boolean literal, not user input
|
||||||
"SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, %s), COALESCE(paused, %s), COALESCE(regions, '') FROM sites",
|
"SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, %s), COALESCE(paused, %s), COALESCE(regions, '') FROM sites",
|
||||||
@@ -123,9 +123,9 @@ func (s *SQLStore) GetSites(ctx context.Context) ([]models.Site, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var sites []models.Site
|
var sites []models.SiteConfig
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var st models.Site
|
var st models.SiteConfig
|
||||||
if err := rows.Scan(&st.ID, &st.Name, &st.URL, &st.Type, &st.Token, &st.Interval, &st.AlertID,
|
if err := rows.Scan(&st.ID, &st.Name, &st.URL, &st.Type, &st.Token, &st.Interval, &st.AlertID,
|
||||||
&st.CheckSSL, &st.ExpiryThreshold, &st.MaxRetries, &st.Hostname, &st.Port, &st.Timeout,
|
&st.CheckSSL, &st.ExpiryThreshold, &st.MaxRetries, &st.Hostname, &st.Port, &st.Timeout,
|
||||||
&st.Method, &st.Description, &st.ParentID, &st.AcceptedCodes, &st.DNSResolveType,
|
&st.Method, &st.Description, &st.ParentID, &st.AcceptedCodes, &st.DNSResolveType,
|
||||||
@@ -137,7 +137,7 @@ func (s *SQLStore) GetSites(ctx context.Context) ([]models.Site, error) {
|
|||||||
return sites, rows.Err()
|
return sites, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLStore) AddSite(ctx context.Context, site models.Site) error {
|
func (s *SQLStore) AddSite(ctx context.Context, site models.SiteConfig) error {
|
||||||
token := ""
|
token := ""
|
||||||
if site.Type == "push" {
|
if site.Type == "push" {
|
||||||
var err error
|
var err error
|
||||||
@@ -152,7 +152,7 @@ func (s *SQLStore) AddSite(ctx context.Context, site models.Site) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLStore) UpdateSite(ctx context.Context, site models.Site) error {
|
func (s *SQLStore) UpdateSite(ctx context.Context, site models.SiteConfig) error {
|
||||||
var existingToken string
|
var existingToken string
|
||||||
_ = s.db.QueryRowContext(ctx, s.q("SELECT token FROM sites WHERE id=?"), site.ID).Scan(&existingToken) //nolint:errcheck
|
_ = s.db.QueryRowContext(ctx, s.q("SELECT token FROM sites WHERE id=?"), site.ID).Scan(&existingToken) //nolint:errcheck
|
||||||
if site.Type == "push" && existingToken == "" {
|
if site.Type == "push" && existingToken == "" {
|
||||||
@@ -198,13 +198,13 @@ func (s *SQLStore) DeleteSite(ctx context.Context, id int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLStore) GetSiteByName(ctx context.Context, name string) (models.Site, error) {
|
func (s *SQLStore) GetSiteByName(ctx context.Context, name string) (models.SiteConfig, error) {
|
||||||
bf := s.dialect.BoolFalse()
|
bf := s.dialect.BoolFalse()
|
||||||
query := fmt.Sprintf( //nolint:gosec // bf is a dialect boolean literal, not user input
|
query := fmt.Sprintf( //nolint:gosec // bf is a dialect boolean literal, not user input
|
||||||
"SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, %s), COALESCE(paused, %s), COALESCE(regions, '') FROM sites WHERE name = %s",
|
"SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, %s), COALESCE(paused, %s), COALESCE(regions, '') FROM sites WHERE name = %s",
|
||||||
bf, bf, s.q("?"),
|
bf, bf, s.q("?"),
|
||||||
)
|
)
|
||||||
var st models.Site
|
var st models.SiteConfig
|
||||||
err := s.db.QueryRowContext(ctx, query, name).Scan(&st.ID, &st.Name, &st.URL, &st.Type, &st.Token, &st.Interval, &st.AlertID,
|
err := s.db.QueryRowContext(ctx, query, name).Scan(&st.ID, &st.Name, &st.URL, &st.Type, &st.Token, &st.Interval, &st.AlertID,
|
||||||
&st.CheckSSL, &st.ExpiryThreshold, &st.MaxRetries, &st.Hostname, &st.Port, &st.Timeout,
|
&st.CheckSSL, &st.ExpiryThreshold, &st.MaxRetries, &st.Hostname, &st.Port, &st.Timeout,
|
||||||
&st.Method, &st.Description, &st.ParentID, &st.AcceptedCodes, &st.DNSResolveType,
|
&st.Method, &st.Description, &st.ParentID, &st.AcceptedCodes, &st.DNSResolveType,
|
||||||
@@ -246,7 +246,7 @@ func (s *SQLStore) GetAlertByName(ctx context.Context, name string) (models.Aler
|
|||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLStore) AddSiteReturningID(ctx context.Context, site models.Site) (int, error) {
|
func (s *SQLStore) AddSiteReturningID(ctx context.Context, site models.SiteConfig) (int, error) {
|
||||||
token := ""
|
token := ""
|
||||||
if site.Type == "push" {
|
if site.Type == "push" {
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func TestSiteCRUD(t *testing.T) {
|
|||||||
t.Fatalf("expected 0 sites, got %d", len(sites))
|
t.Fatalf("expected 0 sites, got %d", len(sites))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.AddSite(context.Background(), models.Site{Name: "Test", URL: "https://example.com", Type: "http", Interval: 30}); err != nil {
|
if err := s.AddSite(context.Background(), models.SiteConfig{Name: "Test", URL: "https://example.com", Type: "http", Interval: 30}); err != nil {
|
||||||
t.Fatalf("AddSite: %v", err)
|
t.Fatalf("AddSite: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ func TestUserCRUD(t *testing.T) {
|
|||||||
func TestPushTokenGeneration(t *testing.T) {
|
func TestPushTokenGeneration(t *testing.T) {
|
||||||
s := newTestStore(t)
|
s := newTestStore(t)
|
||||||
|
|
||||||
if err := s.AddSite(context.Background(), models.Site{Name: "Push Monitor", Type: "push", Interval: 60}); err != nil {
|
if err := s.AddSite(context.Background(), models.SiteConfig{Name: "Push Monitor", Type: "push", Interval: 60}); err != nil {
|
||||||
t.Fatalf("AddSite: %v", err)
|
t.Fatalf("AddSite: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ func TestImportExport(t *testing.T) {
|
|||||||
if err := s.AddAlert(context.Background(), "Test Alert", "webhook", map[string]string{"url": "https://example.com"}); err != nil {
|
if err := s.AddAlert(context.Background(), "Test Alert", "webhook", map[string]string{"url": "https://example.com"}); err != nil {
|
||||||
t.Fatalf("AddAlert: %v", err)
|
t.Fatalf("AddAlert: %v", err)
|
||||||
}
|
}
|
||||||
if err := s.AddSite(context.Background(), models.Site{Name: "Site1", URL: "https://example.com", Type: "http", Interval: 30}); err != nil {
|
if err := s.AddSite(context.Background(), models.SiteConfig{Name: "Site1", URL: "https://example.com", Type: "http", Interval: 30}); err != nil {
|
||||||
t.Fatalf("AddSite: %v", err)
|
t.Fatalf("AddSite: %v", err)
|
||||||
}
|
}
|
||||||
if err := s.AddUser(context.Background(), "user1", "ssh-ed25519 KEY", "user"); err != nil {
|
if err := s.AddUser(context.Background(), "user1", "ssh-ed25519 KEY", "user"); err != nil {
|
||||||
@@ -239,7 +239,7 @@ func TestImportExport(t *testing.T) {
|
|||||||
func TestImportData_WipesHistory(t *testing.T) {
|
func TestImportData_WipesHistory(t *testing.T) {
|
||||||
s := newTestStore(t)
|
s := newTestStore(t)
|
||||||
|
|
||||||
if err := s.AddSite(context.Background(), models.Site{Name: "OldSite", URL: "https://old.com", Type: "http", Interval: 30}); err != nil {
|
if err := s.AddSite(context.Background(), models.SiteConfig{Name: "OldSite", URL: "https://old.com", Type: "http", Interval: 30}); err != nil {
|
||||||
t.Fatalf("AddSite: %v", err)
|
t.Fatalf("AddSite: %v", err)
|
||||||
}
|
}
|
||||||
if err := s.SaveCheck(context.Background(), 1, 5000, true); err != nil {
|
if err := s.SaveCheck(context.Background(), 1, 5000, true); err != nil {
|
||||||
@@ -253,7 +253,7 @@ func TestImportData_WipesHistory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
backup := models.Backup{
|
backup := models.Backup{
|
||||||
Sites: []models.Site{{ID: 1, Name: "NewSite", URL: "https://new.com", Type: "http", Interval: 60}},
|
Sites: []models.SiteConfig{{ID: 1, Name: "NewSite", URL: "https://new.com", Type: "http", Interval: 60}},
|
||||||
}
|
}
|
||||||
if err := s.ImportData(context.Background(), backup); err != nil {
|
if err := s.ImportData(context.Background(), backup); err != nil {
|
||||||
t.Fatalf("ImportData: %v", err)
|
t.Fatalf("ImportData: %v", err)
|
||||||
@@ -314,7 +314,7 @@ func TestCheckHistory(t *testing.T) {
|
|||||||
func TestDeleteSiteCascade(t *testing.T) {
|
func TestDeleteSiteCascade(t *testing.T) {
|
||||||
s := newTestStore(t)
|
s := newTestStore(t)
|
||||||
|
|
||||||
site := models.Site{Name: "Cascade Test", URL: "https://example.com", Interval: 30}
|
site := models.SiteConfig{Name: "Cascade Test", URL: "https://example.com", Interval: 30}
|
||||||
if err := s.AddSite(context.Background(), site); err != nil {
|
if err := s.AddSite(context.Background(), site); err != nil {
|
||||||
t.Fatalf("AddSite: %v", err)
|
t.Fatalf("AddSite: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ type Store interface {
|
|||||||
Init(ctx context.Context) error
|
Init(ctx context.Context) error
|
||||||
|
|
||||||
// Sites
|
// Sites
|
||||||
GetSites(ctx context.Context) ([]models.Site, error)
|
GetSites(ctx context.Context) ([]models.SiteConfig, error)
|
||||||
AddSite(ctx context.Context, site models.Site) error
|
AddSite(ctx context.Context, site models.SiteConfig) error
|
||||||
UpdateSite(ctx context.Context, site models.Site) error
|
UpdateSite(ctx context.Context, site models.SiteConfig) error
|
||||||
UpdateSitePaused(ctx context.Context, id int, paused bool) error
|
UpdateSitePaused(ctx context.Context, id int, paused bool) error
|
||||||
DeleteSite(ctx context.Context, id int) error
|
DeleteSite(ctx context.Context, id int) error
|
||||||
|
|
||||||
@@ -25,9 +25,9 @@ type Store interface {
|
|||||||
DeleteAlert(ctx context.Context, id int) error
|
DeleteAlert(ctx context.Context, id int) error
|
||||||
|
|
||||||
// Declarative config support
|
// Declarative config support
|
||||||
GetSiteByName(ctx context.Context, name string) (models.Site, error)
|
GetSiteByName(ctx context.Context, name string) (models.SiteConfig, error)
|
||||||
GetAlertByName(ctx context.Context, name string) (models.AlertConfig, error)
|
GetAlertByName(ctx context.Context, name string) (models.AlertConfig, error)
|
||||||
AddSiteReturningID(ctx context.Context, site models.Site) (int, error)
|
AddSiteReturningID(ctx context.Context, site models.SiteConfig) (int, error)
|
||||||
AddAlertReturningID(ctx context.Context, name, aType string, settings map[string]string) (int, error)
|
AddAlertReturningID(ctx context.Context, name, aType string, settings map[string]string) (int, error)
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import (
|
|||||||
// mocks and override only the methods you need via the exported Func fields or
|
// mocks and override only the methods you need via the exported Func fields or
|
||||||
// by shadowing the method on the embedding struct.
|
// by shadowing the method on the embedding struct.
|
||||||
type BaseMock struct {
|
type BaseMock struct {
|
||||||
GetSitesFunc func(ctx context.Context) ([]models.Site, error)
|
GetSitesFunc func(ctx context.Context) ([]models.SiteConfig, error)
|
||||||
AddSiteFunc func(ctx context.Context, site models.Site) error
|
AddSiteFunc func(ctx context.Context, site models.SiteConfig) error
|
||||||
UpdateSiteFunc func(ctx context.Context, site models.Site) error
|
UpdateSiteFunc func(ctx context.Context, site models.SiteConfig) error
|
||||||
GetAllAlertsFunc func(ctx context.Context) ([]models.AlertConfig, error)
|
GetAllAlertsFunc func(ctx context.Context) ([]models.AlertConfig, error)
|
||||||
GetAlertFunc func(ctx context.Context, id int) (models.AlertConfig, error)
|
GetAlertFunc func(ctx context.Context, id int) (models.AlertConfig, error)
|
||||||
GetAllUsersFunc func(ctx context.Context) ([]models.User, error)
|
GetAllUsersFunc func(ctx context.Context) ([]models.User, error)
|
||||||
@@ -41,21 +41,21 @@ type BaseMock struct {
|
|||||||
func (m *BaseMock) Init(_ context.Context) error { return nil }
|
func (m *BaseMock) Init(_ context.Context) error { return nil }
|
||||||
func (m *BaseMock) Close() error { return nil }
|
func (m *BaseMock) Close() error { return nil }
|
||||||
|
|
||||||
func (m *BaseMock) GetSites(ctx context.Context) ([]models.Site, error) {
|
func (m *BaseMock) GetSites(ctx context.Context) ([]models.SiteConfig, error) {
|
||||||
if m.GetSitesFunc != nil {
|
if m.GetSitesFunc != nil {
|
||||||
return m.GetSitesFunc(ctx)
|
return m.GetSitesFunc(ctx)
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *BaseMock) AddSite(ctx context.Context, site models.Site) error {
|
func (m *BaseMock) AddSite(ctx context.Context, site models.SiteConfig) error {
|
||||||
if m.AddSiteFunc != nil {
|
if m.AddSiteFunc != nil {
|
||||||
return m.AddSiteFunc(ctx, site)
|
return m.AddSiteFunc(ctx, site)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *BaseMock) UpdateSite(ctx context.Context, site models.Site) error {
|
func (m *BaseMock) UpdateSite(ctx context.Context, site models.SiteConfig) error {
|
||||||
if m.UpdateSiteFunc != nil {
|
if m.UpdateSiteFunc != nil {
|
||||||
return m.UpdateSiteFunc(ctx, site)
|
return m.UpdateSiteFunc(ctx, site)
|
||||||
}
|
}
|
||||||
@@ -90,15 +90,17 @@ func (m *BaseMock) UpdateAlert(_ context.Context, _ int, _ string, _ string, _ m
|
|||||||
|
|
||||||
func (m *BaseMock) DeleteAlert(_ context.Context, _ int) error { return nil }
|
func (m *BaseMock) DeleteAlert(_ context.Context, _ int) error { return nil }
|
||||||
|
|
||||||
func (m *BaseMock) GetSiteByName(_ context.Context, _ string) (models.Site, error) {
|
func (m *BaseMock) GetSiteByName(_ context.Context, _ string) (models.SiteConfig, error) {
|
||||||
return models.Site{}, nil
|
return models.SiteConfig{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *BaseMock) GetAlertByName(_ context.Context, _ string) (models.AlertConfig, error) {
|
func (m *BaseMock) GetAlertByName(_ context.Context, _ string) (models.AlertConfig, error) {
|
||||||
return models.AlertConfig{}, nil
|
return models.AlertConfig{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *BaseMock) AddSiteReturningID(_ context.Context, _ models.Site) (int, error) { return 0, nil }
|
func (m *BaseMock) AddSiteReturningID(_ context.Context, _ models.SiteConfig) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *BaseMock) AddAlertReturningID(_ context.Context, _ string, _ string, _ map[string]string) (int, error) {
|
func (m *BaseMock) AddAlertReturningID(_ context.Context, _ string, _ string, _ map[string]string) (int, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
|
|||||||
+13
-13
@@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
func TestSortSitesForDisplay_GroupsFirst(t *testing.T) {
|
func TestSortSitesForDisplay_GroupsFirst(t *testing.T) {
|
||||||
sites := []models.Site{
|
sites := []models.Site{
|
||||||
{ID: 3, Name: "ungrouped", Type: "http", Status: "UP"},
|
{SiteConfig: models.SiteConfig{ID: 3, Name: "ungrouped", Type: "http"}, SiteState: models.SiteState{Status: "UP"}},
|
||||||
{ID: 1, Name: "group-a", Type: "group", Status: "UP"},
|
{SiteConfig: models.SiteConfig{ID: 1, Name: "group-a", Type: "group"}, SiteState: models.SiteState{Status: "UP"}},
|
||||||
{ID: 2, Name: "child", Type: "http", Status: "UP", ParentID: 1},
|
{SiteConfig: models.SiteConfig{ID: 2, Name: "child", Type: "http", ParentID: 1}, SiteState: models.SiteState{Status: "UP"}},
|
||||||
}
|
}
|
||||||
result := sortSitesForDisplay(sites, nil)
|
result := sortSitesForDisplay(sites, nil)
|
||||||
if len(result) != 3 {
|
if len(result) != 3 {
|
||||||
@@ -29,9 +29,9 @@ func TestSortSitesForDisplay_GroupsFirst(t *testing.T) {
|
|||||||
|
|
||||||
func TestSortSitesForDisplay_CollapsedHidesChildren(t *testing.T) {
|
func TestSortSitesForDisplay_CollapsedHidesChildren(t *testing.T) {
|
||||||
sites := []models.Site{
|
sites := []models.Site{
|
||||||
{ID: 1, Name: "group-a", Type: "group", Status: "UP"},
|
{SiteConfig: models.SiteConfig{ID: 1, Name: "group-a", Type: "group"}, SiteState: models.SiteState{Status: "UP"}},
|
||||||
{ID: 2, Name: "child-1", Type: "http", Status: "UP", ParentID: 1},
|
{SiteConfig: models.SiteConfig{ID: 2, Name: "child-1", Type: "http", ParentID: 1}, SiteState: models.SiteState{Status: "UP"}},
|
||||||
{ID: 3, Name: "child-2", Type: "http", Status: "UP", ParentID: 1},
|
{SiteConfig: models.SiteConfig{ID: 3, Name: "child-2", Type: "http", ParentID: 1}, SiteState: models.SiteState{Status: "UP"}},
|
||||||
}
|
}
|
||||||
collapsed := map[int]bool{1: true}
|
collapsed := map[int]bool{1: true}
|
||||||
result := sortSitesForDisplay(sites, collapsed)
|
result := sortSitesForDisplay(sites, collapsed)
|
||||||
@@ -45,9 +45,9 @@ func TestSortSitesForDisplay_CollapsedHidesChildren(t *testing.T) {
|
|||||||
|
|
||||||
func TestSortSitesForDisplay_StatusOrdering(t *testing.T) {
|
func TestSortSitesForDisplay_StatusOrdering(t *testing.T) {
|
||||||
sites := []models.Site{
|
sites := []models.Site{
|
||||||
{ID: 1, Name: "up-site", Type: "http", Status: "UP"},
|
{SiteConfig: models.SiteConfig{ID: 1, Name: "up-site", Type: "http"}, SiteState: models.SiteState{Status: "UP"}},
|
||||||
{ID: 2, Name: "down-site", Type: "http", Status: "DOWN"},
|
{SiteConfig: models.SiteConfig{ID: 2, Name: "down-site", Type: "http"}, SiteState: models.SiteState{Status: "DOWN"}},
|
||||||
{ID: 3, Name: "late-site", Type: "http", Status: "LATE"},
|
{SiteConfig: models.SiteConfig{ID: 3, Name: "late-site", Type: "http"}, SiteState: models.SiteState{Status: "LATE"}},
|
||||||
}
|
}
|
||||||
result := sortSitesForDisplay(sites, nil)
|
result := sortSitesForDisplay(sites, nil)
|
||||||
if result[0].Status != "DOWN" {
|
if result[0].Status != "DOWN" {
|
||||||
@@ -63,9 +63,9 @@ func TestSortSitesForDisplay_StatusOrdering(t *testing.T) {
|
|||||||
|
|
||||||
func TestFilterSites(t *testing.T) {
|
func TestFilterSites(t *testing.T) {
|
||||||
sites := []models.Site{
|
sites := []models.Site{
|
||||||
{Name: "Production API"},
|
{SiteConfig: models.SiteConfig{Name: "Production API"}},
|
||||||
{Name: "Staging API"},
|
{SiteConfig: models.SiteConfig{Name: "Staging API"}},
|
||||||
{Name: "Database"},
|
{SiteConfig: models.SiteConfig{Name: "Database"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -87,7 +87,7 @@ func TestFilterSites(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFilterSites_EmptyNeedle(t *testing.T) {
|
func TestFilterSites_EmptyNeedle(t *testing.T) {
|
||||||
sites := []models.Site{{Name: "a"}, {Name: "b"}}
|
sites := []models.Site{{SiteConfig: models.SiteConfig{Name: "a"}}, {SiteConfig: models.SiteConfig{Name: "b"}}}
|
||||||
got := filterSites(sites, "")
|
got := filterSites(sites, "")
|
||||||
if len(got) != 2 {
|
if len(got) != 2 {
|
||||||
t.Errorf("empty needle should return all, got %d", len(got))
|
t.Errorf("empty needle should return all, got %d", len(got))
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ func TestSiteOrder(t *testing.T) {
|
|||||||
site models.Site
|
site models.Site
|
||||||
want int
|
want int
|
||||||
}{
|
}{
|
||||||
{"down", models.Site{Status: "DOWN"}, 0},
|
{"down", models.Site{SiteState: models.SiteState{Status: "DOWN"}}, 0},
|
||||||
{"ssl exp", models.Site{Status: "SSL EXP"}, 0},
|
{"ssl exp", models.Site{SiteState: models.SiteState{Status: "SSL EXP"}}, 0},
|
||||||
{"late", models.Site{Status: "LATE"}, 1},
|
{"late", models.Site{SiteState: models.SiteState{Status: "LATE"}}, 1},
|
||||||
{"up", models.Site{Status: "UP"}, 2},
|
{"up", models.Site{SiteState: models.SiteState{Status: "UP"}}, 2},
|
||||||
{"pending", models.Site{Status: "PENDING"}, 3},
|
{"pending", models.Site{SiteState: models.SiteState{Status: "PENDING"}}, 3},
|
||||||
{"paused up", models.Site{Status: "UP", Paused: true}, 3},
|
{"paused up", models.Site{SiteConfig: models.SiteConfig{Paused: true}, SiteState: models.SiteState{Status: "UP"}}, 3},
|
||||||
{"paused down", models.Site{Status: "DOWN", Paused: true}, 3},
|
{"paused down", models.Site{SiteConfig: models.SiteConfig{Paused: true}, SiteState: models.SiteState{Status: "DOWN"}}, 3},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
got := siteOrder(tt.site)
|
got := siteOrder(tt.site)
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ func (m *Model) submitSiteForm() tea.Cmd {
|
|||||||
threshold = 7
|
threshold = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
site := models.Site{
|
cfg := models.SiteConfig{
|
||||||
ID: m.editID,
|
ID: m.editID,
|
||||||
Name: d.Name,
|
Name: d.Name,
|
||||||
URL: d.URL,
|
URL: d.URL,
|
||||||
@@ -559,11 +559,8 @@ func (m *Model) submitSiteForm() tea.Cmd {
|
|||||||
st := m.store
|
st := m.store
|
||||||
m.state = stateDashboard
|
m.state = stateDashboard
|
||||||
if m.editID > 0 {
|
if m.editID > 0 {
|
||||||
// The engine's in-memory config updates immediately; the DB write
|
m.engine.UpdateSiteConfig(cfg)
|
||||||
// follows in the Cmd. New sites enter the engine via its poll loop
|
return writeCmd("Update site", func() error { return st.UpdateSite(context.Background(), cfg) })
|
||||||
// once the insert lands.
|
|
||||||
m.engine.UpdateSiteConfig(site)
|
|
||||||
return writeCmd("Update site", func() error { return st.UpdateSite(context.Background(), site) })
|
|
||||||
}
|
}
|
||||||
return writeCmd("Add site", func() error { return st.AddSite(context.Background(), site) })
|
return writeCmd("Add site", func() error { return st.AddSite(context.Background(), cfg) })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func (*stubErr) Error() string { return "boom" }
|
|||||||
func TestDetailLoad_CachesAndViewDoesNoIO(t *testing.T) {
|
func TestDetailLoad_CachesAndViewDoesNoIO(t *testing.T) {
|
||||||
ms := &tuiMockStore{stateChanges: []models.StateChange{{FromStatus: "UP", ToStatus: "DOWN"}}}
|
ms := &tuiMockStore{stateChanges: []models.StateChange{{FromStatus: "UP", ToStatus: "DOWN"}}}
|
||||||
m := newTestModel(ms)
|
m := newTestModel(ms)
|
||||||
m.sites = []models.Site{{ID: 1, Name: "site", Status: "DOWN"}}
|
m.sites = []models.Site{{SiteConfig: models.SiteConfig{ID: 1, Name: "site"}, SiteState: models.SiteState{Status: "DOWN"}}}
|
||||||
m.cursor = 0
|
m.cursor = 0
|
||||||
m.state = stateDetail
|
m.state = stateDetail
|
||||||
m.termWidth = 120
|
m.termWidth = 120
|
||||||
@@ -201,7 +201,7 @@ func TestHandleTabData_DropsStaleSeq(t *testing.T) {
|
|||||||
func TestHistoryKey_LoadsOffUIGoroutine(t *testing.T) {
|
func TestHistoryKey_LoadsOffUIGoroutine(t *testing.T) {
|
||||||
ms := &tuiMockStore{stateChanges: []models.StateChange{{FromStatus: "UP", ToStatus: "DOWN"}}}
|
ms := &tuiMockStore{stateChanges: []models.StateChange{{FromStatus: "UP", ToStatus: "DOWN"}}}
|
||||||
m := newTestModel(ms)
|
m := newTestModel(ms)
|
||||||
m.sites = []models.Site{{ID: 7, Name: "site"}}
|
m.sites = []models.Site{{SiteConfig: models.SiteConfig{ID: 7, Name: "site"}}}
|
||||||
m.state = stateDetail
|
m.state = stateDetail
|
||||||
m.termWidth, m.termHeight = 120, 40
|
m.termWidth, m.termHeight = 120, 40
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ func TestHistoryKey_LoadsOffUIGoroutine(t *testing.T) {
|
|||||||
func TestSLAData_DropsStaleReply(t *testing.T) {
|
func TestSLAData_DropsStaleReply(t *testing.T) {
|
||||||
m := newTestModel(&tuiMockStore{})
|
m := newTestModel(&tuiMockStore{})
|
||||||
m.termWidth, m.termHeight = 120, 40
|
m.termWidth, m.termHeight = 120, 40
|
||||||
m.sites = []models.Site{{ID: 3, Status: "UP"}}
|
m.sites = []models.Site{{SiteConfig: models.SiteConfig{ID: 3}, SiteState: models.SiteState{Status: "UP"}}}
|
||||||
|
|
||||||
if cmd := (&m).openSLAView(m.sites[0]); cmd == nil {
|
if cmd := (&m).openSLAView(m.sites[0]); cmd == nil {
|
||||||
t.Fatal("openSLAView should return a load Cmd")
|
t.Fatal("openSLAView should return a load Cmd")
|
||||||
@@ -264,7 +264,7 @@ func TestSLAData_DropsStaleReply(t *testing.T) {
|
|||||||
func TestConfirmDelete_WritesOffUIGoroutine(t *testing.T) {
|
func TestConfirmDelete_WritesOffUIGoroutine(t *testing.T) {
|
||||||
ms := &tuiMockStore{}
|
ms := &tuiMockStore{}
|
||||||
m := newTestModel(ms)
|
m := newTestModel(ms)
|
||||||
m.sites = []models.Site{{ID: 4, Name: "s"}}
|
m.sites = []models.Site{{SiteConfig: models.SiteConfig{ID: 4, Name: "s"}}}
|
||||||
m.state = stateConfirmDelete
|
m.state = stateConfirmDelete
|
||||||
m.deleteTab = 0
|
m.deleteTab = 0
|
||||||
m.deleteID = 4
|
m.deleteID = 4
|
||||||
@@ -312,7 +312,7 @@ func TestWriteDoneMsg_LogsErrorAndReloads(t *testing.T) {
|
|||||||
func TestDetailRefreshCmd_OnlyWhileDetailOpen(t *testing.T) {
|
func TestDetailRefreshCmd_OnlyWhileDetailOpen(t *testing.T) {
|
||||||
ms := &tuiMockStore{stateChanges: []models.StateChange{{FromStatus: "UP", ToStatus: "DOWN"}}}
|
ms := &tuiMockStore{stateChanges: []models.StateChange{{FromStatus: "UP", ToStatus: "DOWN"}}}
|
||||||
m := newTestModel(ms)
|
m := newTestModel(ms)
|
||||||
m.sites = []models.Site{{ID: 5, Name: "site"}}
|
m.sites = []models.Site{{SiteConfig: models.SiteConfig{ID: 5, Name: "site"}}}
|
||||||
|
|
||||||
m.state = stateDashboard
|
m.state = stateDashboard
|
||||||
if (&m).detailRefreshCmd() != nil {
|
if (&m).detailRefreshCmd() != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user