D1: deploy.sh CI gate — read-only SHA via git ls-remote, Gitea commit-status
poll, portable mkdir deploy lock (macOS, no flock), TOCTOU guard, token
passed via curl --config - (not argv), graceful misconfig tolerance.
D2: version-tagged images — OIKOS_VERSION=v$VERSION, keep-last-3 prune derived
from 'docker compose config --images'; VERSION read after pull.
D3: per-IP rate limiting — new internal/httpapi/ratelimit.go (x/time/rate),
rightmost-XFF, /healthz exempt, ctx-driven sweep; disabled by default.
D4: mem_limit/cpus on all 10 compose services.
D5: staleness-aware health probes — new internal/health package wired into
scheduler (:8093) and notifier (:8094); nomos already had :8092.
Two /review passes hardened the deploy lock, TOCTOU guard, token hygiene,
and XFF handling.
74 lines
1.8 KiB
Go
74 lines
1.8 KiB
Go
package health
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestProbeHealthyAtBoot(t *testing.T) {
|
|
p := New(time.Minute)
|
|
if !p.Healthy() {
|
|
t.Fatal("probe should be healthy immediately after creation")
|
|
}
|
|
}
|
|
|
|
func TestProbeStaleAfterWindow(t *testing.T) {
|
|
p := New(50 * time.Millisecond)
|
|
time.Sleep(80 * time.Millisecond)
|
|
if p.Healthy() {
|
|
t.Fatal("probe should be stale after the staleness window elapses with no Bump")
|
|
}
|
|
p.Bump()
|
|
if !p.Healthy() {
|
|
t.Fatal("probe should recover immediately after Bump")
|
|
}
|
|
}
|
|
|
|
func TestProbeHandlerStatusCodes(t *testing.T) {
|
|
p := New(20 * time.Millisecond)
|
|
|
|
// Fresh → 200
|
|
if code := probeCode(p); code != http.StatusOK {
|
|
t.Fatalf("fresh probe: want 200, got %d", code)
|
|
}
|
|
|
|
// Stale → 503
|
|
time.Sleep(40 * time.Millisecond)
|
|
if code := probeCode(p); code != http.StatusServiceUnavailable {
|
|
t.Fatalf("stale probe: want 503, got %d", code)
|
|
}
|
|
}
|
|
|
|
func TestProbeHandlerBody(t *testing.T) {
|
|
p := New(time.Minute)
|
|
rec := httptest.NewRecorder()
|
|
p.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/healthz", nil))
|
|
|
|
var body map[string]any
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
|
t.Fatalf("invalid JSON body: %v (body=%q)", err, rec.Body.String())
|
|
}
|
|
if body["status"] != "ok" {
|
|
t.Fatalf("want status=ok, got %v", body["status"])
|
|
}
|
|
if _, ok := body["last_heartbeat"].(string); !ok {
|
|
t.Fatalf("want last_heartbeat string, got %v", body["last_heartbeat"])
|
|
}
|
|
}
|
|
|
|
func TestNewDefaultsStale(t *testing.T) {
|
|
p := New(0)
|
|
if p.stale <= 0 {
|
|
t.Fatal("New(0) should fall back to a positive staleness window")
|
|
}
|
|
}
|
|
|
|
func probeCode(p *Probe) int {
|
|
rec := httptest.NewRecorder()
|
|
p.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/healthz", nil))
|
|
return rec.Code
|
|
}
|