Public source validation / validate (push) Failing after 3m8s
196 lines
8.8 KiB
Go
196 lines
8.8 KiB
Go
package systemstatus
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/config"
|
|
"github.com/itworx/pulse/internal/datasource"
|
|
)
|
|
|
|
func TestBuildNeverReportsUnsampledSourcesAsHealthy(t *testing.T) {
|
|
snapshot := Build(config.Config{AuthMode: "mock", PrometheusURL: "http://prometheus", UnraidURL: "http://unraid", UnraidAPIToken: "configured"}, true, time.Unix(100, 0), nil)
|
|
if snapshot.OverallState != StateUnknown {
|
|
t.Fatalf("overall state = %q, want unknown", snapshot.OverallState)
|
|
}
|
|
for _, component := range snapshot.Components {
|
|
if component.ID == "prometheus" || component.ID == "unraid" {
|
|
if component.State == StateHealthy {
|
|
t.Fatalf("unsampled source %s reported healthy", component.ID)
|
|
}
|
|
}
|
|
}
|
|
if snapshot.Backup.State != StateUnknown || snapshot.Backup.Reason != "no_verified_backup" {
|
|
t.Fatalf("backup status = %#v", snapshot.Backup)
|
|
}
|
|
}
|
|
|
|
func TestObservedAgentSourcesOverrideMissingAPILocalCredentials(t *testing.T) {
|
|
now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
|
|
observed := datasource.SourceHealth{
|
|
State: datasource.HealthHealthy, ObservedAt: now.Add(-time.Second), ReceivedAt: now, LastSuccess: now.Add(-time.Second),
|
|
Policy: datasource.FreshnessPolicy{MaxAge: time.Minute}, ReasonCode: "source_sampled",
|
|
}
|
|
snapshot := Build(config.Config{AuthMode: "mock"}, true, now, nil, WithSources(
|
|
FromDatasource("unraid", observed, now),
|
|
FromDatasource("storage", observed, now),
|
|
))
|
|
for _, id := range []string{"unraid", "storage"} {
|
|
component := componentByID(snapshot, id)
|
|
if component.State != StateHealthy || component.Reason != "source_sampled" || component.ObservedAt == nil {
|
|
t.Fatalf("component %s = %#v", id, component)
|
|
}
|
|
}
|
|
if len(snapshot.SourceLag) != 1 || snapshot.SourceLag[0].SourceID != "unraid" || snapshot.SourceLag[0].State != StateHealthy {
|
|
t.Fatalf("source lag = %#v", snapshot.SourceLag)
|
|
}
|
|
}
|
|
|
|
func TestConfiguredOIDCReflectsAuthenticatedProtectedRequest(t *testing.T) {
|
|
now := time.Date(2026, 8, 12, 9, 0, 0, 0, time.UTC)
|
|
cfg := config.Config{AuthMode: "oidc", OIDCIssuer: "https://auth.example", OIDCClientID: "pulse"}
|
|
|
|
withoutSession := Build(cfg, true, now, nil)
|
|
if got := componentByID(withoutSession, "oidc"); got.State != StateUnknown || got.Reason != "authenticated_session_not_observed" {
|
|
t.Fatalf("OIDC without request-local session proof = %#v", got)
|
|
}
|
|
|
|
withSession := Build(cfg, true, now, nil, WithAuthenticatedSession(true))
|
|
got := componentByID(withSession, "oidc")
|
|
if got.State != StateHealthy || got.Reason != "authenticated_session" || got.ObservedAt == nil || got.LastSuccessAt == nil {
|
|
t.Fatalf("OIDC with authenticated session = %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestRequiredDisabledSourcePreventsHealthyOverallState(t *testing.T) {
|
|
now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
|
|
jobs := []JobHealth{
|
|
{Component: ComponentWorker, Status: JobCompleted, LastRunAt: now, LastSuccessAt: now},
|
|
{Component: ComponentProbes, Status: JobCompleted, LastRunAt: now, LastSuccessAt: now},
|
|
{Component: ComponentNotifications, Status: JobCompleted, LastRunAt: now, LastSuccessAt: now},
|
|
}
|
|
snapshot := Build(config.Config{AuthMode: "mock"}, true, now, nil, WithJobs(time.Minute, jobs...))
|
|
if snapshot.OverallState != StateUnknown {
|
|
t.Fatalf("overall state = %q, want unknown", snapshot.OverallState)
|
|
}
|
|
}
|
|
|
|
func TestBackupObservationNeverTreatsStaleOrFailedAsHealthy(t *testing.T) {
|
|
now := time.Date(2026, 8, 12, 4, 0, 0, 0, time.UTC)
|
|
fresh := Build(config.Config{AuthMode: "mock"}, true, now, nil, WithBackupObservation(now.Add(-time.Hour), now.Add(-time.Minute), nil), WithMigrationVersion("0019_capacity_history"))
|
|
if fresh.Backup.State != StateHealthy || fresh.Backup.Reason != "backup_verified" || fresh.Backup.AgeSeconds == nil || fresh.Backup.VerifiedAt == nil || fresh.Backup.VerifiedAt.Equal(*fresh.Backup.LastSuccessAt) || fresh.Release.MigrationVersion != "0019_capacity_history" {
|
|
t.Fatalf("fresh snapshot = %#v", fresh)
|
|
}
|
|
stale := Build(config.Config{AuthMode: "mock"}, true, now, nil, WithBackupObservation(now.Add(-48*time.Hour), now, nil))
|
|
if stale.Backup.State == StateHealthy || stale.Backup.Reason != "backup_stale" {
|
|
t.Fatalf("stale backup = %#v", stale.Backup)
|
|
}
|
|
failed := Build(config.Config{AuthMode: "mock"}, true, now, nil, WithBackupObservation(time.Time{}, time.Time{}, errors.New("checksum mismatch")))
|
|
if failed.Backup.State != StateDegraded || failed.Backup.Reason != "backup_verification_failed" || failed.OverallState != StateDegraded {
|
|
t.Fatalf("failed backup = %#v", failed.Backup)
|
|
}
|
|
}
|
|
|
|
func TestBuildSortsComponentsAndDisablesUnconfiguredSources(t *testing.T) {
|
|
snapshot := Build(config.Config{AuthMode: "mock"}, false, time.Unix(100, 0), nil)
|
|
if snapshot.Components[0].ID != "database" {
|
|
t.Fatalf("components are not deterministic: %#v", snapshot.Components)
|
|
}
|
|
for _, component := range snapshot.Components {
|
|
if component.ID == "prometheus" || component.ID == "unraid" {
|
|
if component.State != StateDisabled {
|
|
t.Fatalf("component %s = %q, want disabled", component.ID, component.State)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func componentByID(snapshot Snapshot, id string) Component {
|
|
for _, component := range snapshot.Components {
|
|
if component.ID == id {
|
|
return component
|
|
}
|
|
}
|
|
return Component{}
|
|
}
|
|
|
|
func TestBackgroundJobComponentsReportRealOutcomes(t *testing.T) {
|
|
now := time.Date(2026, 8, 4, 12, 0, 0, 0, time.UTC)
|
|
cfg := config.Config{AuthMode: "mock"}
|
|
for name, testCase := range map[string]struct {
|
|
jobs []JobHealth
|
|
wantState State
|
|
wantReason string
|
|
}{
|
|
"never reported": {
|
|
jobs: nil,
|
|
wantState: StateUnknown, wantReason: "heartbeat_not_recorded",
|
|
},
|
|
"recent success": {
|
|
jobs: []JobHealth{{Component: ComponentWorker, JobKey: "discovery", Status: JobCompleted, LastRunAt: now.Add(-time.Minute), LastSuccessAt: now.Add(-time.Minute)}},
|
|
wantState: StateHealthy, wantReason: "last_run_succeeded",
|
|
},
|
|
"failure is degraded": {
|
|
jobs: []JobHealth{{Component: ComponentWorker, JobKey: "discovery", Status: JobFailed, ErrorCode: "lease_unavailable", LastRunAt: now}},
|
|
wantState: StateDegraded, wantReason: "lease_unavailable",
|
|
},
|
|
"stale success is unknown": {
|
|
jobs: []JobHealth{{Component: ComponentWorker, JobKey: "discovery", Status: JobCompleted, LastRunAt: now.Add(-time.Hour), LastSuccessAt: now.Add(-time.Hour)}},
|
|
wantState: StateUnknown, wantReason: "last_success_stale",
|
|
},
|
|
"completed without a success is unknown": {
|
|
jobs: []JobHealth{{Component: ComponentWorker, JobKey: "discovery", Status: JobCompleted, LastRunAt: now}},
|
|
wantState: StateUnknown, wantReason: "last_success_not_recorded",
|
|
},
|
|
"not configured is disabled": {
|
|
jobs: []JobHealth{{Component: ComponentWorker, JobKey: "discovery", Status: JobDisabled, Reason: "container_source_not_registered", LastRunAt: now}},
|
|
wantState: StateDisabled, wantReason: "container_source_not_registered",
|
|
},
|
|
"worst job wins": {
|
|
jobs: []JobHealth{
|
|
{Component: ComponentWorker, JobKey: "discovery", Status: JobCompleted, LastRunAt: now, LastSuccessAt: now},
|
|
{Component: ComponentWorker, JobKey: "alert-evaluation", Status: JobFailed, ErrorCode: "run_failed", LastRunAt: now},
|
|
},
|
|
wantState: StateDegraded, wantReason: "run_failed",
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
snapshot := Build(cfg, true, now, nil, WithJobs(5*time.Minute, testCase.jobs...))
|
|
worker := componentByID(snapshot, ComponentWorker)
|
|
if worker.State != testCase.wantState || worker.Reason != testCase.wantReason {
|
|
t.Fatalf("worker component = %#v", worker)
|
|
}
|
|
if worker.State == StateHealthy && worker.LastSuccessAt == nil {
|
|
t.Fatal("a healthy component must carry the observation that justifies it")
|
|
}
|
|
// Components without a reported job keep their conservative default.
|
|
probes := componentByID(snapshot, ComponentProbes)
|
|
if probes.State != StateUnknown || probes.Reason != "probe_heartbeat_not_recorded" {
|
|
t.Fatalf("probes component = %#v", probes)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOverallStateStaysUnknownUntilEveryComponentReports(t *testing.T) {
|
|
now := time.Date(2026, 8, 4, 12, 0, 0, 0, time.UTC)
|
|
cfg := config.Config{AuthMode: "mock"}
|
|
jobs := []JobHealth{
|
|
{Component: ComponentWorker, Status: JobCompleted, LastRunAt: now, LastSuccessAt: now},
|
|
{Component: ComponentProbes, Status: JobCompleted, LastRunAt: now, LastSuccessAt: now},
|
|
{Component: ComponentNotifications, Status: JobCompleted, LastRunAt: now, LastSuccessAt: now},
|
|
}
|
|
if state := Build(cfg, true, now, nil, WithJobs(time.Minute, jobs[:1]...)).OverallState; state != StateUnknown {
|
|
t.Fatalf("overall state with one reporting job = %q", state)
|
|
}
|
|
snapshot := Build(cfg, true, now, nil, WithJobs(time.Minute, jobs...))
|
|
for _, component := range snapshot.Components {
|
|
if component.ID == ComponentWorker || component.ID == ComponentProbes || component.ID == ComponentNotifications {
|
|
if component.State != StateHealthy {
|
|
t.Fatalf("component %s = %#v", component.ID, component)
|
|
}
|
|
}
|
|
}
|
|
}
|