package cudp import ( "encoding/json" "net" "sync" "testing" "time" "gorm.io/datatypes" "rocketgit.ru/rsmon/worker/app/models" ) type settings struct { Host string `json:"host,omitempty"` Port string `json:"port,omitempty"` Timeout int `json:"timeout,omitempty"` Count int `json:"count,omitempty"` PacketSize int `json:"packet_size,omitempty"` } func newCheck(t *testing.T, s settings, host string) *models.Check { t.Helper() raw, err := json.Marshal(s) if err != nil { t.Fatalf("marshal: %v", err) } return &models.Check{ Kind: "udp", Monitor: &models.Monitor{Host: host}, Settings: datatypes.JSON(raw), } } // startEchoUDP brings up a UDP listener on 127.0.0.1:0 that echoes // the first byte back to the sender. Returns the listener and the // resolved address. Stop it with the returned cleanup. func startEchoUDP(t *testing.T) (cleanup func(), addr string) { t.Helper() conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) if err != nil { t.Fatalf("listen udp: %v", err) } stop := make(chan struct{}) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() buf := make([]byte, 1500) for { select { case <-stop: return default: } _ = conn.SetReadDeadline(time.Now().Add(50 * time.Millisecond)) n, src, err := conn.ReadFromUDP(buf) if err != nil { continue } if n == 0 { continue } _, _ = conn.WriteToUDP(buf[:1], src) } }() return func() { close(stop) wg.Wait() _ = conn.Close() }, conn.LocalAddr().String() } func TestPerformOKWhenServerReplies(t *testing.T) { cleanup, addr := startEchoUDP(t) defer cleanup() host, port, err := net.SplitHostPort(addr) if err != nil { t.Fatalf("split: %v", err) } c := newCheck(t, settings{Port: port, Timeout: 2}, host) r := Perform(c) if r.State != stateOK { t.Fatalf("expected OK when the server echoes back, got %s (err=%v, warn=%v)", r.State, r.Error, r.Warnings) } if r.RemoteAddr == "" { t.Fatalf("expected RemoteAddr, got %q", r.RemoteAddr) } } func TestPerformWarnWhenNoReply(t *testing.T) { // Open a UDP listener that never replies; the probe should // time out and the check should report WARN to signal "reachable, // ambiguous service". conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) if err != nil { t.Fatalf("listen: %v", err) } defer conn.Close() // Drain the listener so the kernel knows the port is in use but // never reply; just close the read side and let the kernel drop // incoming datagrams. go func() { buf := make([]byte, 1500) for { _, _, _ = conn.ReadFromUDP(buf) } }() host, port, _ := net.SplitHostPort(conn.LocalAddr().String()) c := newCheck(t, settings{Port: port, Timeout: 1}, host) r := Perform(c) if r.State != stateWARN { t.Fatalf("expected WARN when the server is silent, got %s (err=%v)", r.State, r.Error) } if len(r.Warnings) == 0 { t.Fatalf("expected a warning describing the silence, got %v", r.Warnings) } } func TestPerformUnreachable(t *testing.T) { c := newCheck(t, settings{Port: "65530", Timeout: 1}, "127.0.0.99") r := Perform(c) if r.State == stateOK { t.Fatalf("expected ERR/WARN, got OK") } } func TestPerformEmptyHost(t *testing.T) { c := newCheck(t, settings{Port: "53", Timeout: 1}, "") r := Perform(c) if r.State != stateERR { t.Fatalf("expected ERR on empty host, got %s", r.State) } } func TestIsTimeout(t *testing.T) { if isTimeout(nil) { t.Fatalf("isTimeout(nil) should be false") } if !isTimeout(timeoutErr{}) { t.Fatalf("isTimeout(timeoutErr) should be true") } if isTimeout(plainErr{}) { t.Fatalf("isTimeout(plainErr) should be false") } } type timeoutErr struct{} func (timeoutErr) Error() string { return "i/o timeout" } func (timeoutErr) Timeout() bool { return true } func (timeoutErr) Temporary() bool { return true } type plainErr struct{} func (plainErr) Error() string { return "boom" } func TestMin(t *testing.T) { if got := minInt(1, 2); got != 1 { t.Fatalf("minInt(1,2) = %d, want 1", got) } if got := minInt(2, 1); got != 1 { t.Fatalf("minInt(2,1) = %d, want 1", got) } if got := minInt(0, 0); got != 0 { t.Fatalf("minInt(0,0) = %d, want 0", got) } }