97 lines
3.4 KiB
Go
97 lines
3.4 KiB
Go
//go:build integration
|
|
|
|
package link
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
"github.com/novox/mesh-controller/internal/testbus"
|
|
)
|
|
|
|
func aBusWithEvents(t *testing.T) *Server {
|
|
t.Helper()
|
|
js, err := broker.Dial(testbus.URL(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(js.Close)
|
|
if err := broker.AssertMeshStreams(js); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return ConnectNats(js, nil, nil)
|
|
}
|
|
|
|
func TestNatsBusFaultsAreOnePerUserAndKindEachClosureCountedOnce(t *testing.T) {
|
|
s := aBusWithEvents(t)
|
|
now := time.Now().UTC()
|
|
slow := BusClosure{User: "laptop.claude-code", Name: "laptop.claude-code", CID: 10, Reason: "Slow Consumer (Write Deadline)",
|
|
Opened: now.Add(-20 * time.Minute), Closed: now.Add(-2 * time.Minute), Pending: 334000}
|
|
sayClosure(t, s, BusSlowConsumerEvent, slow)
|
|
sayClosure(t, s, BusSlowConsumerEvent, slow) // said again: the module started again
|
|
again := slow
|
|
again.CID, again.Opened, again.Closed = 31, now.Add(-90*time.Second), now.Add(-time.Minute)
|
|
sayClosure(t, s, BusSlowConsumerEvent, again)
|
|
sayClosure(t, s, BusConnectionClosedEvent, BusClosure{User: "anchor.desk", CID: 7,
|
|
Reason: "Maximum Message Payload Exceeded", Opened: now.Add(-time.Hour / 2), Closed: now.Add(-30 * time.Second)})
|
|
// Older than the hour: S9 no longer says it.
|
|
sayClosure(t, s, BusSlowConsumerEvent, BusClosure{User: "anchor.media", CID: 3, Reason: "Slow Consumer (Pending Bytes)",
|
|
Opened: now.Add(-3 * time.Hour), Closed: now.Add(-2 * time.Hour)})
|
|
|
|
got, err := s.BusFaults(t.Context(), now.Add(-time.Hour))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("read %d advisories, want one for each user within the hour: %+v", len(got), got)
|
|
}
|
|
for _, a := range got {
|
|
switch a.User {
|
|
case "laptop.claude-code":
|
|
if a.Kind != AdvisorySlowConsumer || a.ID != "laptop.claude-code" || a.Count != 2 ||
|
|
!a.Last.Equal(again.Closed) || !a.First.Equal(slow.Closed) || !strings.Contains(a.Said, "connection 31") {
|
|
t.Errorf("the slow consumer read as %+v: two closures, the one said twice counted once", a)
|
|
}
|
|
case "anchor.desk":
|
|
if a.Kind != AdvisoryConnectionClosed || a.Count != 1 || a.Reason != "Maximum Message Payload Exceeded" {
|
|
t.Errorf("the closed connection read as %+v", a)
|
|
}
|
|
default:
|
|
t.Errorf("an advisory for %s", a.User)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNatsBusFaultsReadNothingWhenTheBusModuleSaidNothing(t *testing.T) {
|
|
s := aBusWithEvents(t)
|
|
started := time.Now()
|
|
got, err := s.BusFaults(t.Context(), time.Now().Add(-time.Hour))
|
|
if err != nil || len(got) != 0 {
|
|
t.Fatalf("read %v, %v from a stream the bus module never wrote", got, err)
|
|
}
|
|
if took := time.Since(started); took > time.Second {
|
|
t.Fatalf("reading nothing took %s: a tick must not wait on an empty stream", took)
|
|
}
|
|
}
|
|
|
|
func TestNatsBusFaultsReadAtMostTheBoundAndSaySo(t *testing.T) {
|
|
s := aBusWithEvents(t)
|
|
was := MaxBusEventsRead
|
|
MaxBusEventsRead = 2
|
|
t.Cleanup(func() { MaxBusEventsRead = was })
|
|
now := time.Now().UTC()
|
|
for i := range 3 {
|
|
sayClosure(t, s, BusSlowConsumerEvent, BusClosure{User: "laptop.claude-code", CID: uint64(10 + i),
|
|
Reason: "Slow Consumer (Pending Bytes)", Opened: now.Add(-time.Hour / 2), Closed: now.Add(-time.Duration(3-i) * time.Minute)})
|
|
}
|
|
got, err := s.BusFaults(t.Context(), now.Add(-time.Hour))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 1 || got[0].Count != 2 || !strings.Contains(got[0].Said, "at least: the read stopped at 2") {
|
|
t.Fatalf("a read past its bound read as %+v", got)
|
|
}
|
|
}
|