0a6d1b8d17
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
161 lines
5.2 KiB
Go
161 lines
5.2 KiB
Go
package infra
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
// findNatsSample devuelve el primer PromSample cuyo Name coincide y cuyos labels
|
|
// extra (clave/valor alternados) están todos presentes con el valor esperado.
|
|
// El segundo retorno indica si se encontró.
|
|
func findNatsSample(samples []PromSample, name string, labels ...string) (PromSample, bool) {
|
|
for _, s := range samples {
|
|
if s.Name != name {
|
|
continue
|
|
}
|
|
match := true
|
|
for i := 0; i+1 < len(labels); i += 2 {
|
|
if s.Labels[labels[i]] != labels[i+1] {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
if match {
|
|
return s, true
|
|
}
|
|
}
|
|
return PromSample{}, false
|
|
}
|
|
|
|
func mustRead(t *testing.T, path string) []byte {
|
|
t.Helper()
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read fixture %s: %v", path, err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// golden: fixtures reales de un nats-server 2.11.15, node="probe" (== el leader
|
|
// de los streams), valores concretos verificados a mano.
|
|
func TestParseNatsMonitorGolden(t *testing.T) {
|
|
varz := mustRead(t, "testdata/nats_varz.json")
|
|
connz := mustRead(t, "testdata/nats_connz.json")
|
|
jsz := mustRead(t, "testdata/nats_jsz.json")
|
|
|
|
got, err := ParseNatsMonitor("probe", varz, connz, jsz)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
want := map[string]float64{
|
|
"nats_msgs_in_total": 17,
|
|
"nats_msgs_out_total": 17,
|
|
"nats_mem_bytes": 18288640,
|
|
"nats_jetstream_streams": 3,
|
|
"nats_connections": 1,
|
|
"nats_jetstream_messages": 6,
|
|
}
|
|
for name, w := range want {
|
|
s, ok := findNatsSample(got, name)
|
|
if !ok {
|
|
t.Errorf("missing sample %q", name)
|
|
continue
|
|
}
|
|
if s.Value != w {
|
|
t.Errorf("%s = %v, want %v", name, s.Value, w)
|
|
}
|
|
if s.Labels["node"] != "probe" || s.Labels["instance"] != "probe" {
|
|
t.Errorf("%s labels = %v, want node=instance=probe", name, s.Labels)
|
|
}
|
|
}
|
|
|
|
// kv_bucket_msgs por cada KV bucket (prefijo KV_ recortado).
|
|
for bucket, w := range map[string]float64{
|
|
"UNIBUS_users": 2,
|
|
"UNIBUS_rooms": 2,
|
|
"UNIBUS_members": 2,
|
|
} {
|
|
s, ok := findNatsSample(got, "kv_bucket_msgs", "bucket", bucket)
|
|
if !ok {
|
|
t.Errorf("missing kv_bucket_msgs{bucket=%q}", bucket)
|
|
continue
|
|
}
|
|
if s.Value != w {
|
|
t.Errorf("kv_bucket_msgs{bucket=%q} = %v, want %v", bucket, s.Value, w)
|
|
}
|
|
}
|
|
|
|
// raft leader: probe == node, así que el stream KV_UNIBUS_users tiene leader=1.
|
|
s, ok := findNatsSample(got, "nats_jetstream_raft_leader", "stream", "KV_UNIBUS_users")
|
|
if !ok {
|
|
t.Fatal("missing nats_jetstream_raft_leader{stream=KV_UNIBUS_users}")
|
|
}
|
|
if s.Value != 1 {
|
|
t.Errorf("nats_jetstream_raft_leader{stream=KV_UNIBUS_users} = %v, want 1", s.Value)
|
|
}
|
|
|
|
// stream_detail también emite nats_stream_messages con label stream completo.
|
|
if s, ok := findNatsSample(got, "nats_stream_messages", "stream", "KV_UNIBUS_users"); !ok || s.Value != 2 {
|
|
t.Errorf("nats_stream_messages{stream=KV_UNIBUS_users} = %v ok=%v, want 2", s.Value, ok)
|
|
}
|
|
|
|
// nats_server_start_seconds presente (start es RFC3339 válido).
|
|
if _, ok := findNatsSample(got, "nats_server_start_seconds"); !ok {
|
|
t.Error("missing nats_server_start_seconds (start is a valid RFC3339)")
|
|
}
|
|
}
|
|
|
|
// edge: jsz sin streams ni account_details. No produce series kv_bucket_msgs ni
|
|
// nats_stream_*, pero sí las de varz/connz y las jetstream top-level (en 0).
|
|
func TestParseNatsMonitorEmptyJsz(t *testing.T) {
|
|
varz := mustRead(t, "testdata/nats_varz.json")
|
|
connz := mustRead(t, "testdata/nats_connz.json")
|
|
jsz := []byte(`{"streams":0,"account_details":[]}`)
|
|
|
|
got, err := ParseNatsMonitor("probe", varz, connz, jsz)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if _, ok := findNatsSample(got, "kv_bucket_msgs", "bucket", "UNIBUS_users"); ok {
|
|
t.Error("did not expect kv_bucket_msgs with empty account_details")
|
|
}
|
|
if _, ok := findNatsSample(got, "nats_stream_messages"); ok {
|
|
t.Error("did not expect nats_stream_messages with empty account_details")
|
|
}
|
|
// varz/connz siguen presentes.
|
|
if s, ok := findNatsSample(got, "nats_msgs_in_total"); !ok || s.Value != 17 {
|
|
t.Errorf("nats_msgs_in_total = %v ok=%v, want 17", s.Value, ok)
|
|
}
|
|
if s, ok := findNatsSample(got, "nats_connections"); !ok || s.Value != 1 {
|
|
t.Errorf("nats_connections = %v ok=%v, want 1", s.Value, ok)
|
|
}
|
|
}
|
|
|
|
// edge: connz inválido. No es error; nats_connections cae a varz.connections (1).
|
|
// varz/jsz siguen produciendo sus series.
|
|
func TestParseNatsMonitorInvalidConnz(t *testing.T) {
|
|
varz := mustRead(t, "testdata/nats_varz.json")
|
|
jsz := mustRead(t, "testdata/nats_jsz.json")
|
|
|
|
got, err := ParseNatsMonitor("probe", varz, []byte("not json"), jsz)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
// fallback a varz.connections (= 1).
|
|
if s, ok := findNatsSample(got, "nats_connections"); !ok || s.Value != 1 {
|
|
t.Errorf("nats_connections = %v ok=%v, want 1 (fallback varz.connections)", s.Value, ok)
|
|
}
|
|
// jsz sigue vivo.
|
|
if s, ok := findNatsSample(got, "nats_jetstream_streams"); !ok || s.Value != 3 {
|
|
t.Errorf("nats_jetstream_streams = %v ok=%v, want 3", s.Value, ok)
|
|
}
|
|
}
|
|
|
|
// error path: varz inválido devuelve error no-nil (es el core, sin él no hay nada).
|
|
func TestParseNatsMonitorInvalidVarz(t *testing.T) {
|
|
if _, err := ParseNatsMonitor("probe", []byte("{{{"), nil, nil); err == nil {
|
|
t.Fatal("expected error for invalid varz, got nil")
|
|
}
|
|
}
|