package cping import ( "encoding/json" "errors" "strings" "testing" "time" "gorm.io/datatypes" "rocketgit.ru/rsmon/worker/app/models" ) // settings mirrors models.CheckSettings so the test can build a // CheckSettings JSON without pulling in the entire model package. type settings struct { Host string `json:"host,omitempty"` Count int `json:"count,omitempty"` Timeout int `json:"timeout,omitempty"` PacketSize int `json:"packet_size,omitempty"` Port string `json:"port,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 settings: %v", err) } if host == "" { host = "127.0.0.1" } return &models.Check{ Kind: "ping", Monitor: &models.Monitor{Host: host}, Settings: datatypes.JSON(raw), } } // fakePinger returns canned stats without touching the network so the // state machine in Perform() can be exercised in CI environments // without CAP_NET_RAW. type fakePinger struct { stats Stats } func (f *fakePinger) Run(host string, count int, timeout time.Duration, payloadSize int) Stats { return f.stats } func TestPerformOK(t *testing.T) { SetPinger(&fakePinger{stats: Stats{PacketsSent: 3, PacketsRecv: 3, AvgRtt: 4 * time.Millisecond}}) defer SetPinger(&realPinger{}) c := newCheck(t, settings{Count: 3}, "") r := Perform(c) if r.State != stateOK { t.Fatalf("expected OK, got %s (err=%v)", r.State, r.Error) } if r.PacketsSent != 3 || r.PacketsRecv != 3 { t.Fatalf("packet counts wrong: sent=%d recv=%d", r.PacketsSent, r.PacketsRecv) } if r.AvgRttMs < 1 { t.Fatalf("expected RTT > 0, got %v", r.AvgRttMs) } if len(r.Infos) == 0 || !strings.Contains(r.Infos[0], "rtt=") { t.Fatalf("expected rtt info line, got %v", r.Infos) } } func TestPerformNoReply(t *testing.T) { SetPinger(&fakePinger{stats: Stats{PacketsSent: 3, PacketsRecv: 0, Err: errors.New("i/o timeout")}}) defer SetPinger(&realPinger{}) c := newCheck(t, settings{}, "") r := Perform(c) if r.State != stateERR { t.Fatalf("expected ERR, got %s", r.State) } if r.PacketsSent == 0 { t.Fatalf("expected PacketsSent to be incremented even on failure") } if r.Error == nil { t.Fatalf("expected error, got nil") } } func TestPerformUnsupported(t *testing.T) { SetPinger(&fakePinger{stats: Stats{Err: errors.New("socket: operation not permitted (cap_net_raw)")}}) defer SetPinger(&realPinger{}) c := newCheck(t, settings{}, "") r := Perform(c) if r.State != stateFail { t.Fatalf("expected FAIL when raw ICMP is not allowed, got %s", r.State) } } func TestPerformEmptyHost(t *testing.T) { c := &models.Check{ Kind: "ping", Monitor: &models.Monitor{Host: ""}, Settings: datatypes.JSON("{}"), } r := Perform(c) if r.State != stateFail { t.Fatalf("expected FAIL on empty host, got %s", r.State) } } func TestIsUnsupported(t *testing.T) { cases := []struct { err error want bool }{ {err: nil, want: false}, {err: errors.New(""), want: false}, {err: errors.New("permission denied"), want: true}, {err: errors.New("icmp listen: cap_net_raw required"), want: true}, {err: errors.New("i/o timeout"), want: false}, {err: errors.New("no route to host"), want: false}, } for _, tc := range cases { if got := isUnsupported(tc.err); got != tc.want { t.Errorf("isUnsupported(%q) = %v, want %v", tc.err, got, tc.want) } } } func TestLossPercent(t *testing.T) { cases := []struct { sent, recv int want int64 }{ {sent: 0, recv: 0, want: 0}, {sent: 5, recv: 5, want: 0}, {sent: 5, recv: 3, want: 40}, {sent: 5, recv: 0, want: 100}, } for _, tc := range cases { if got := lossPercent(tc.sent, tc.recv); got != tc.want { t.Errorf("lossPercent(%d,%d) = %d, want %d", tc.sent, tc.recv, got, tc.want) } } } func TestMakeBytes(t *testing.T) { if got := makeBytes(0); len(got) != 0 { t.Fatalf("expected empty slice, got %d bytes", len(got)) } if got := makeBytes(-1); len(got) != 0 { t.Fatalf("expected empty slice for negative size, got %d bytes", len(got)) } got := makeBytes(3) if len(got) != 3 || string(got) != "abc" { t.Fatalf("expected 'abc', got %q", string(got)) } }