- 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>
220 lines
5.5 KiB
Go
220 lines
5.5 KiB
Go
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")
|
|
}
|
|
}
|