package embeddednats import ( "io" "net" "net/http" "testing" "time" ) // TestNatsLogOptsDecoupled is the core regression guard for issue 0007: turning // on the monitoring endpoint must NEVER turn on the verbose nats-server debug log // (which would leak room subjects/routing metadata to journald). It also checks // the backward-compatible coupling (debug still implies monitoring) and the quiet // default. func TestNatsLogOptsDecoupled(t *testing.T) { cases := []struct { name string debugEnv, monitorEnv string noLog, debug, trace, monitor bool }{ {"default off — quiet, no monitor", "", "", true, false, false, false}, {"monitor only — endpoint on, log stays quiet", "", "1", true, false, false, true}, {"debug implies monitor", "1", "", false, true, false, true}, {"trace implies debug+monitor", "2", "", false, true, true, true}, {"both set", "1", "1", false, true, false, true}, {"monitor garbage value ignored", "", "yes", true, false, false, false}, {"debug garbage value ignored", "true", "", true, false, false, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { noLog, debug, trace, monitor := natsLogOpts(c.debugEnv, c.monitorEnv) if noLog != c.noLog || debug != c.debug || trace != c.trace || monitor != c.monitor { t.Fatalf("natsLogOpts(%q,%q) = (noLog=%v debug=%v trace=%v monitor=%v), want (noLog=%v debug=%v trace=%v monitor=%v)", c.debugEnv, c.monitorEnv, noLog, debug, trace, monitor, c.noLog, c.debug, c.trace, c.monitor) } }) } // Explicit golden assertion of the security property: monitor on, log off. noLog, debug, _, monitor := natsLogOpts("", "1") if !monitor { t.Fatal("UNIBUS_NATS_MONITOR=1 must open the monitoring endpoint") } if debug || !noLog { t.Fatalf("UNIBUS_NATS_MONITOR=1 must NOT enable the debug log (got debug=%v noLog=%v)", debug, noLog) } } // TestMonitorEndpointLoopback boots a real embedded server with // UNIBUS_NATS_MONITOR=1 (and DEBUG explicitly off) and proves the monitoring HTTP // endpoint answers on loopback only — the exact contract the metrics scraper // relies on. The pure decoupling check above already guarantees the log stays out // of debug mode for this same env combination. func TestMonitorEndpointLoopback(t *testing.T) { t.Setenv("UNIBUS_NATS_DEBUG", "") t.Setenv("UNIBUS_NATS_MONITOR", "1") ns, err := StartServer(ServerConfig{ StoreDir: t.TempDir(), Host: "127.0.0.1", Port: freeLoopbackPort(t), }) if err != nil { t.Fatalf("start server with monitoring: %v", err) } defer func() { ns.Shutdown(); ns.WaitForShutdown() }() addr := ns.MonitorAddr() if addr == nil { t.Fatal("monitoring endpoint not open with UNIBUS_NATS_MONITOR=1 (MonitorAddr is nil)") } if !addr.IP.IsLoopback() { t.Fatalf("monitoring endpoint bound to %s, must be loopback only", addr.IP) } if addr.Port != 8222 { t.Fatalf("monitoring endpoint on port %d, want the fixed loopback port 8222", addr.Port) } // /varz must answer 200 with a non-empty body on loopback. url := "http://" + addr.String() + "/varz" var resp *http.Response deadline := time.Now().Add(3 * time.Second) for time.Now().Before(deadline) { resp, err = http.Get(url) //nolint:gosec // loopback monitoring endpoint, no auth by design if err == nil { break } time.Sleep(50 * time.Millisecond) } if err != nil { t.Fatalf("GET %s: %v", url, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("GET %s -> %d, want 200", url, resp.StatusCode) } body, _ := io.ReadAll(resp.Body) if len(body) == 0 { t.Fatalf("GET %s returned an empty body", url) } } // TestMonitorDisabledByDefault proves a server started without either toggle does // NOT open the monitoring endpoint, so production stays closed unless opted in. func TestMonitorDisabledByDefault(t *testing.T) { t.Setenv("UNIBUS_NATS_DEBUG", "") t.Setenv("UNIBUS_NATS_MONITOR", "") ns, err := StartServer(ServerConfig{ StoreDir: t.TempDir(), Host: "127.0.0.1", Port: freeLoopbackPort(t), }) if err != nil { t.Fatalf("start server: %v", err) } defer func() { ns.Shutdown(); ns.WaitForShutdown() }() if addr := ns.MonitorAddr(); addr != nil { t.Fatalf("monitoring endpoint open (%s) without UNIBUS_NATS_MONITOR — must stay closed by default", addr) } } func freeLoopbackPort(t *testing.T) int { t.Helper() l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("free port: %v", err) } defer l.Close() return l.Addr().(*net.TCPAddr).Port }