test(18-04): add failing tests for DiscoveryResponder

- Subscribe/unsubscribe lifecycle
- Invalid JSON returns error response
- Missing ip_address returns descriptive error
- Response JSON field names match spec
- Invalid SNMP version rejected
- Default port 161 when zero

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-21 19:25:23 -05:00
parent 8d3952e92d
commit 727598cc78

View File

@@ -0,0 +1,219 @@
package bus
import (
"encoding/json"
"strings"
"testing"
"time"
)
func TestDiscoveryResponder_Subscribe(t *testing.T) {
nc, cleanup := startTestNATS(t)
defer cleanup()
dr := NewDiscoveryResponder(nc)
if err := dr.Start(); err != nil {
t.Fatalf("Start() returned error: %v", err)
}
defer dr.Stop()
if dr.sub == nil {
t.Fatal("expected subscription to be set after Start()")
}
// Verify subscription subject and queue group
if dr.sub.Subject != "device.discover.snmp" {
t.Errorf("expected subject 'device.discover.snmp', got %q", dr.sub.Subject)
}
if dr.sub.Queue != "discover-workers" {
t.Errorf("expected queue 'discover-workers', got %q", dr.sub.Queue)
}
}
func TestDiscoveryResponder_InvalidJSON(t *testing.T) {
nc, cleanup := startTestNATS(t)
defer cleanup()
dr := NewDiscoveryResponder(nc)
if err := dr.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
defer dr.Stop()
reply, err := nc.Request("device.discover.snmp", []byte("{invalid json"), 5*time.Second)
if err != nil {
t.Fatalf("NATS request failed: %v", err)
}
var resp DiscoveryResponse
if err := json.Unmarshal(reply.Data, &resp); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if resp.Error == "" {
t.Error("expected non-empty error for invalid JSON")
}
if !strings.Contains(resp.Error, "invalid request") {
t.Errorf("expected error to contain 'invalid request', got %q", resp.Error)
}
}
func TestDiscoveryResponder_MissingIPAddress(t *testing.T) {
nc, cleanup := startTestNATS(t)
defer cleanup()
dr := NewDiscoveryResponder(nc)
if err := dr.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
defer dr.Stop()
req := DiscoveryRequest{
SNMPVersion: "v2c",
Community: "public",
}
reqData, _ := json.Marshal(req)
reply, err := nc.Request("device.discover.snmp", reqData, 5*time.Second)
if err != nil {
t.Fatalf("NATS request failed: %v", err)
}
var resp DiscoveryResponse
if err := json.Unmarshal(reply.Data, &resp); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if resp.Error == "" {
t.Error("expected non-empty error for missing ip_address")
}
if !strings.Contains(resp.Error, "ip_address") {
t.Errorf("expected error to mention 'ip_address', got %q", resp.Error)
}
}
func TestDiscoveryResponder_ResponseFields(t *testing.T) {
// Verify the DiscoveryResponse JSON field names match the spec.
resp := DiscoveryResponse{
SysObjectID: "1.3.6.1.4.1.14988.1",
SysDescr: "RouterOS RB750Gr3",
SysName: "router1.local",
}
data, err := json.Marshal(resp)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var raw map[string]interface{}
if err := json.Unmarshal(data, &raw); err != nil {
t.Fatalf("unmarshal to map: %v", err)
}
for _, field := range []string{"sys_object_id", "sys_descr", "sys_name"} {
if _, ok := raw[field]; !ok {
t.Errorf("expected field %q in JSON output", field)
}
}
// Verify error field with omitempty
errResp := DiscoveryResponse{Error: "test error"}
errData, _ := json.Marshal(errResp)
var errRaw map[string]interface{}
_ = json.Unmarshal(errData, &errRaw)
if _, ok := errRaw["error"]; !ok {
t.Error("expected 'error' field in error response JSON")
}
}
func TestDiscoveryResponder_Stop_Unsubscribes(t *testing.T) {
nc, cleanup := startTestNATS(t)
defer cleanup()
dr := NewDiscoveryResponder(nc)
if err := dr.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
if !dr.sub.IsValid() {
t.Fatal("expected subscription to be valid before Stop()")
}
dr.Stop()
if dr.sub.IsValid() {
t.Error("expected subscription to be invalid after Stop()")
}
}
func TestDiscoveryResponder_InvalidSNMPVersion(t *testing.T) {
nc, cleanup := startTestNATS(t)
defer cleanup()
dr := NewDiscoveryResponder(nc)
if err := dr.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
defer dr.Stop()
req := DiscoveryRequest{
IPAddress: "10.0.0.1",
SNMPVersion: "v4",
}
reqData, _ := json.Marshal(req)
reply, err := nc.Request("device.discover.snmp", reqData, 5*time.Second)
if err != nil {
t.Fatalf("NATS request failed: %v", err)
}
var resp DiscoveryResponse
if err := json.Unmarshal(reply.Data, &resp); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if resp.Error == "" {
t.Error("expected non-empty error for invalid snmp_version")
}
}
func TestDiscoveryResponder_DefaultPort(t *testing.T) {
// Test that requests with zero port default to 161.
// We verify this indirectly -- the request should not fail validation
// (it will fail on SNMP connect, which is expected since there's no device).
nc, cleanup := startTestNATS(t)
defer cleanup()
dr := NewDiscoveryResponder(nc)
if err := dr.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
defer dr.Stop()
req := DiscoveryRequest{
IPAddress: "192.0.2.1", // TEST-NET, unreachable
SNMPVersion: "v2c",
Community: "public",
// SNMPPort intentionally 0 -- should default to 161
}
reqData, _ := json.Marshal(req)
reply, err := nc.Request("device.discover.snmp", reqData, 10*time.Second)
if err != nil {
t.Fatalf("NATS request failed: %v", err)
}
var resp DiscoveryResponse
if err := json.Unmarshal(reply.Data, &resp); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
// The request passed validation (no "ip_address" or "snmp_version" error).
// It should fail on SNMP probe (unreachable device), not on validation.
if resp.Error != "" && strings.Contains(resp.Error, "ip_address") {
t.Error("request with zero port should not fail ip_address validation")
}
if resp.Error != "" && strings.Contains(resp.Error, "snmp_version") {
t.Error("request with zero port should not fail snmp_version validation")
}
}