cache: add SQLite route persistence; initial TTL and LRU eviction implementation
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I0370d6c114d5490634905c1a831a31526a6a6964
This commit is contained in:
parent
9f264fbef1
commit
663f9995b2
8 changed files with 674 additions and 5 deletions
100
internal/prober/prober_test.go
Normal file
100
internal/prober/prober_test.go
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package prober_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"notashelf.dev/ncro/internal/prober"
|
||||
)
|
||||
|
||||
func TestEMACalculation(t *testing.T) {
|
||||
p := prober.New(0.3)
|
||||
p.RecordLatency("https://example.com", 100)
|
||||
p.RecordLatency("https://example.com", 50)
|
||||
|
||||
// EMA after 2 measurements: first=100, second = 0.3*50 + 0.7*100 = 85
|
||||
health := p.GetHealth("https://example.com")
|
||||
if health == nil {
|
||||
t.Fatal("expected health entry")
|
||||
}
|
||||
if health.EMALatency < 84 || health.EMALatency > 86 {
|
||||
t.Errorf("EMA = %.2f, want ~85", health.EMALatency)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusProgression(t *testing.T) {
|
||||
p := prober.New(0.3)
|
||||
p.RecordLatency("https://example.com", 10)
|
||||
|
||||
for range 3 {
|
||||
p.RecordFailure("https://example.com")
|
||||
}
|
||||
h := p.GetHealth("https://example.com")
|
||||
if h.Status != prober.StatusDegraded {
|
||||
t.Errorf("status = %v, want Degraded after 3 failures", h.Status)
|
||||
}
|
||||
|
||||
for range 7 {
|
||||
p.RecordFailure("https://example.com")
|
||||
}
|
||||
h = p.GetHealth("https://example.com")
|
||||
if h.Status != prober.StatusDown {
|
||||
t.Errorf("status = %v, want Down after 10 failures", h.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecoveryAfterSuccess(t *testing.T) {
|
||||
p := prober.New(0.3)
|
||||
for range 10 {
|
||||
p.RecordFailure("https://example.com")
|
||||
}
|
||||
p.RecordLatency("https://example.com", 20)
|
||||
h := p.GetHealth("https://example.com")
|
||||
if h.Status != prober.StatusActive {
|
||||
t.Errorf("status = %v, want Active after recovery", h.Status)
|
||||
}
|
||||
if h.ConsecutiveFails != 0 {
|
||||
t.Errorf("ConsecutiveFails = %d, want 0", h.ConsecutiveFails)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortedByLatency(t *testing.T) {
|
||||
p := prober.New(0.3)
|
||||
p.RecordLatency("https://slow.example.com", 200)
|
||||
p.RecordLatency("https://fast.example.com", 10)
|
||||
p.RecordLatency("https://medium.example.com", 50)
|
||||
|
||||
sorted := p.SortedByLatency()
|
||||
if len(sorted) != 3 {
|
||||
t.Fatalf("expected 3, got %d", len(sorted))
|
||||
}
|
||||
if sorted[0].URL != "https://fast.example.com" {
|
||||
t.Errorf("first = %q, want fast", sorted[0].URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeUpstream(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
p := prober.New(0.3)
|
||||
p.ProbeUpstream(srv.URL)
|
||||
|
||||
h := p.GetHealth(srv.URL)
|
||||
if h == nil || h.Status != prober.StatusActive {
|
||||
t.Errorf("expected Active after successful probe, got %v", h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeUpstreamFailure(t *testing.T) {
|
||||
p := prober.New(0.3)
|
||||
p.ProbeUpstream("http://127.0.0.1:1") // nothing listening
|
||||
|
||||
h := p.GetHealth("http://127.0.0.1:1")
|
||||
if h == nil || h.ConsecutiveFails == 0 {
|
||||
t.Error("expected failure recorded")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue