Files
EvoBGP/internal/httpapi/peers_live_test.go
T
DenozordecandCursor f8e0520817 feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь.

Co-authored-by: Cursor <[email protected]>
2026-11-02 17:23:59 +07:00

153 lines
6.6 KiB
Go

package httpapi
import (
"testing"
"time"
"evobgp/internal/birdfmt"
"evobgp/internal/store"
)
func TestApplyPeerLiveFieldsObservationStates(t *testing.T) {
peer := &store.BGPPeer{ID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", Neighbor: "198.51.100.2"}
now := time.Date(2026, 10, 26, 10, 0, 0, 0, time.UTC)
cases := []struct {
name string
views []speakerBGPLive
wantState any
wantQuality string
}{
{"no collector", nil, nil, "unknown"},
{"successful empty poll", []speakerBGPLive{{Polled: true, Source: "agent_bird_protocols", ObservedAt: now, ReceivedAt: now, LastSuccessAt: now}}, "absent", "ok"},
{"first failure", []speakerBGPLive{{Polled: true, Error: "timeout", Source: "agent_bird_protocols", ObservedAt: now}}, nil, "unreachable"},
{"failure after success", []speakerBGPLive{{Polled: true, Error: "timeout", Source: "agent_bird_protocols", ObservedAt: now, LastSuccessAt: now.Add(-time.Minute)}}, nil, "stale"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
row := map[string]any{"session_state": "Established"}
applyPeerLiveFields(row, peer, tc.views)
if row["session_state"] != tc.wantState || row["last_known_session_state"] != "Established" {
t.Fatalf("session_state=%v last_known=%v", row["session_state"], row["last_known_session_state"])
}
observation := row["live_observation"].(liveObservation)
if observation.Quality != tc.wantQuality {
t.Fatalf("observation=%+v", observation)
}
})
}
}
func TestApplyPeerLiveFieldsExpectedSpeakerCoverage(t *testing.T) {
now := time.Date(2026, 10, 26, 10, 0, 0, 0, time.UTC)
assigned := "speaker-a"
peer := &store.BGPPeer{ID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", Neighbor: "198.51.100.2", SpeakerID: &assigned}
viewAError := speakerBGPLive{SpeakerID: assigned, Polled: true, Error: "timeout", ObservedAt: now, Source: "agent_bird_protocols"}
viewAEmpty := speakerBGPLive{SpeakerID: assigned, Polled: true, ObservedAt: now, LastSuccessAt: now, Source: "agent_bird_protocols"}
viewBEmpty := speakerBGPLive{SpeakerID: "speaker-b", Polled: true, ObservedAt: now.Add(time.Minute), LastSuccessAt: now.Add(time.Minute), Source: "agent_bird_protocols"}
viewBError := speakerBGPLive{SpeakerID: "speaker-b", Polled: true, Error: "timeout", ObservedAt: now.Add(time.Minute), Source: "agent_bird_protocols"}
cases := []struct {
name string
assigned bool
views []speakerBGPLive
wantState any
wantQuality string
wantObserved string
}{
{"assigned timeout unrelated success", true, []speakerBGPLive{viewAError, viewBEmpty}, nil, "unreachable", now.Format(time.RFC3339Nano)},
{"assigned success unrelated failure", true, []speakerBGPLive{viewAEmpty, viewBError}, "absent", "ok", now.Format(time.RFC3339Nano)},
{"global mixed outcome", false, []speakerBGPLive{viewAEmpty, viewBError}, nil, "unknown", now.Add(time.Minute).Format(time.RFC3339Nano)},
{"assigned speaker not observed", true, []speakerBGPLive{viewBEmpty}, nil, "unknown", ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
candidate := *peer
if !tc.assigned {
candidate.SpeakerID = nil
}
row := map[string]any{"session_state": "Established"}
applyPeerLiveFields(row, &candidate, tc.views)
obs := row["live_observation"].(liveObservation)
if row["session_state"] != tc.wantState || obs.Quality != tc.wantQuality {
t.Fatalf("state=%v observation=%+v", row["session_state"], obs)
}
if tc.wantObserved == "" && obs.ObservedAt != nil {
t.Fatalf("unexpected timestamp %v", *obs.ObservedAt)
}
if tc.wantObserved != "" && (obs.ObservedAt == nil || *obs.ObservedAt != tc.wantObserved) {
t.Fatalf("observed_at=%v want=%q", obs.ObservedAt, tc.wantObserved)
}
})
}
}
func TestMatchPeerOnSpeakers_establishedOnReplica(t *testing.T) {
peer := &store.BGPPeer{
ID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
Neighbor: "198.51.100.2",
}
views := []speakerBGPLive{
{
SpeakerID: "master-id",
Label: "CP · bgp.shz.su",
Sessions: []birdfmt.BGPSession{
{Name: birdfmt.PeerProtocolName(peer.ID), Neighbor: "198.51.100.2", State: "Established"},
},
},
{
SpeakerID: "replica-id",
Label: "bgp2.shz.su",
Sessions: []birdfmt.BGPSession{
{Name: birdfmt.PeerProtocolName(peer.ID), Neighbor: "198.51.100.2", State: "Established"},
},
},
}
state, connID, connLabel, established, on, mismatch := matchPeerOnSpeakers(peer, views)
if state != "Established" || connID != "" || connLabel != "CP · bgp.shz.su, bgp2.shz.su" {
t.Fatalf("got state=%q conn=%q label=%q", state, connID, connLabel)
}
if mismatch || len(on) != 2 || len(established) != 2 {
t.Fatalf("on=%+v established=%+v mismatch=%v", on, established, mismatch)
}
}
func TestMatchPeerOnSpeakers_multipleEstablished(t *testing.T) {
peer := &store.BGPPeer{ID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", Neighbor: "198.51.100.2/32"}
views := []speakerBGPLive{
{SpeakerID: "a", Label: "n1", Sessions: []birdfmt.BGPSession{{Name: birdfmt.PeerProtocolName(peer.ID), State: "Established"}}},
{SpeakerID: "b", Label: "n2", Sessions: []birdfmt.BGPSession{{Name: birdfmt.PeerProtocolName(peer.ID), State: "Established"}}},
}
_, connID, label, established, on, mismatch := matchPeerOnSpeakers(peer, views)
if mismatch || connID != "" || label != "n1, n2" || len(established) != 2 || len(on) != 2 {
t.Fatalf("connID=%q label=%q established=%+v on=%+v mismatch=%v", connID, label, established, on, mismatch)
}
}
func TestMatchPeerOnSpeakers_mismatchConfiguredSpeaker(t *testing.T) {
replica := "replica-id"
peer := &store.BGPPeer{
ID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
Neighbor: "198.51.100.2",
SpeakerID: &replica,
}
views := []speakerBGPLive{
{SpeakerID: "master-id", Label: "CP", Sessions: []birdfmt.BGPSession{{Name: birdfmt.PeerProtocolName(peer.ID), State: "Established"}}},
{SpeakerID: replica, Label: "bgp2", Sessions: []birdfmt.BGPSession{{Name: birdfmt.PeerProtocolName(peer.ID), State: "Idle"}}},
}
_, _, _, _, _, mismatch := matchPeerOnSpeakers(peer, views)
if !mismatch {
t.Fatal("expected mismatch when configured replica has no Established")
}
}
func TestMatchPeerOnSpeakers_pollError(t *testing.T) {
peer := &store.BGPPeer{ID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", Neighbor: "198.51.100.2"}
views := []speakerBGPLive{
{Label: "CP (local BIRD)", Sessions: []birdfmt.BGPSession{{Name: birdfmt.PeerProtocolName(peer.ID), State: "Established"}}},
{SpeakerID: "replica-id", Label: "bgp2.shz.su", Error: "HTTP 404: Not Found"},
}
_, _, _, established, on, _ := matchPeerOnSpeakers(peer, views)
if len(established) != 1 || len(on) != 2 || on[1].PollError == "" {
t.Fatalf("on=%+v established=%+v", on, established)
}
}