mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 19:41:17 +00:00
Related to #614 Corrects three issues with PMG monitoring: 1. Remove unsupported timeframe parameter from GetMailStatistics - PMG API /statistics/mail does not accept timeframe parameter - Previously sent "timeframe=day" causing 400 error - API returns current day statistics by default 2. Fix GetMailCount timespan parameter to use seconds - Changed from 24 (hours) to 86400 (seconds) - PMG API expects timespan in seconds, not hours - Previously sent "timespan=24" causing 400 error 3. Update function signature and tests - Renamed GetMailCount parameter from timespanHours to timespanSeconds - Updated test expectations to match corrected API calls - Tests verify parameters are sent correctly These changes align the PMG client with actual PMG API requirements, fixing the data population issues reported in v4.25.0.
227 lines
7.1 KiB
Go
227 lines
7.1 KiB
Go
package monitoring
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
"github.com/rcourtman/pulse-go-rewrite/pkg/pmg"
|
|
)
|
|
|
|
func TestPollPMGInstancePopulatesState(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/api2/json/access/ticket":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(w, `{"data":{"ticket":"ticket123","CSRFPreventionToken":"csrf123"}}`)
|
|
|
|
case "/api2/json/version":
|
|
if !strings.Contains(r.Header.Get("Cookie"), "PMGAuthCookie=ticket123") {
|
|
t.Fatalf("expected auth cookie, got %s", r.Header.Get("Cookie"))
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(w, `{"data":{"version":"8.3.1","release":"1"}}`)
|
|
|
|
case "/api2/json/config/cluster/status":
|
|
if r.URL.Query().Get("list_single_node") != "1" {
|
|
t.Fatalf("expected list_single_node query, got %s", r.URL.RawQuery)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(w, `{"data":[{"cid":1,"name":"mail-gateway","type":"master","ip":"10.0.0.1"}]}`)
|
|
|
|
case "/api2/json/statistics/mail":
|
|
// PMG API does not accept timeframe parameter
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(w, `{"data":{"count":100,"count_in":60,"count_out":40,"spamcount_in":5,"spamcount_out":2,"viruscount_in":1,"viruscount_out":0,"bounces_in":3,"bounces_out":1,"bytes_in":12345,"bytes_out":54321,"glcount":7,"junk_in":4,"avptime":0.5,"rbl_rejects":2,"pregreet_rejects":1}}`)
|
|
|
|
case "/api2/json/statistics/mailcount":
|
|
if r.URL.Query().Get("timespan") != "86400" {
|
|
t.Fatalf("expected timespan=86400 (24 hours in seconds), got %s", r.URL.RawQuery)
|
|
}
|
|
now := time.Now().Unix()
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprintf(w, `{"data":[{"index":0,"time":%d,"count":100,"count_in":60,"count_out":40,"spamcount_in":5,"spamcount_out":2,"viruscount_in":1,"viruscount_out":0,"bounces_in":3,"bounces_out":1,"rbl_rejects":2,"pregreet_rejects":1,"glcount":7}]}`, now)
|
|
|
|
case "/api2/json/statistics/spamscores":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(w, `{"data":[{"level":"low","count":10,"ratio":0.1}]}`)
|
|
|
|
case "/api2/json/quarantine/spamstatus":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(w, `{"data":{"count":5,"avgbytes":0,"mbytes":0}}`)
|
|
|
|
case "/api2/json/quarantine/virusstatus":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(w, `{"data":{"count":2,"avgbytes":0,"mbytes":0}}`)
|
|
|
|
case "/api2/json/nodes/mail-gateway/postfix/queue":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(w, `{"data":{"active":5,"deferred":2,"hold":0,"maildrop":0}}`)
|
|
|
|
case "/api2/json/nodes/mail-gateway/backup":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
timestamp := time.Now().Add(-8 * 24 * time.Hour).Unix()
|
|
fmt.Fprintf(w, `{"data":[{"filename":"pmg-backup_2024-01-01.tgz","size":123456,"timestamp":%d}]}`, timestamp)
|
|
|
|
default:
|
|
t.Fatalf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, err := pmg.NewClient(pmg.ClientConfig{
|
|
Host: server.URL,
|
|
User: "api@pmg",
|
|
Password: "secret",
|
|
VerifySSL: false,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected client error: %v", err)
|
|
}
|
|
|
|
cfg := &config.Config{
|
|
PMGInstances: []config.PMGInstance{
|
|
{
|
|
Name: "primary",
|
|
Host: server.URL,
|
|
User: "api@pmg",
|
|
Password: "secret",
|
|
MonitorMailStats: true,
|
|
MonitorQueues: true,
|
|
MonitorQuarantine: true,
|
|
MonitorDomainStats: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
mon, err := New(cfg)
|
|
if err != nil {
|
|
t.Fatalf("failed to create monitor: %v", err)
|
|
}
|
|
|
|
mon.pollPMGInstance(context.Background(), "primary", client)
|
|
|
|
snapshot := mon.state.GetSnapshot()
|
|
|
|
if len(snapshot.PMGInstances) != 1 {
|
|
t.Fatalf("expected 1 PMG instance in state, got %d", len(snapshot.PMGInstances))
|
|
}
|
|
|
|
instance := snapshot.PMGInstances[0]
|
|
|
|
if instance.Status != "online" {
|
|
t.Fatalf("expected PMG status online, got %s", instance.Status)
|
|
}
|
|
|
|
if instance.ConnectionHealth != "healthy" {
|
|
t.Fatalf("expected connection health healthy, got %s", instance.ConnectionHealth)
|
|
}
|
|
|
|
if instance.MailStats == nil || instance.MailStats.CountTotal != 100 {
|
|
t.Fatalf("expected mail stats totals, got %+v", instance.MailStats)
|
|
}
|
|
|
|
if len(instance.MailCount) != 1 {
|
|
t.Fatalf("expected 1 mail count point, got %d", len(instance.MailCount))
|
|
}
|
|
|
|
if len(instance.SpamDistribution) != 1 {
|
|
t.Fatalf("expected 1 spam distribution bucket, got %d", len(instance.SpamDistribution))
|
|
}
|
|
|
|
if instance.Quarantine == nil || instance.Quarantine.Spam != 5 || instance.Quarantine.Virus != 2 {
|
|
t.Fatalf("expected quarantine counts, got %+v", instance.Quarantine)
|
|
}
|
|
|
|
if health := snapshot.ConnectionHealth["pmg-primary"]; !health {
|
|
t.Fatalf("expected connection health tracked as healthy, got %v", health)
|
|
}
|
|
|
|
if failures := mon.authFailures["pmg-primary"]; failures != 0 {
|
|
t.Fatalf("expected no auth failures tracked, got %d", failures)
|
|
}
|
|
|
|
if len(snapshot.PMGBackups) != 1 {
|
|
t.Fatalf("expected 1 PMG backup in state, got %d", len(snapshot.PMGBackups))
|
|
}
|
|
|
|
pmgBackup := snapshot.PMGBackups[0]
|
|
if pmgBackup.Node != "mail-gateway" {
|
|
t.Fatalf("expected PMG backup node mail-gateway, got %s", pmgBackup.Node)
|
|
}
|
|
}
|
|
|
|
func TestPollPMGInstanceRecordsAuthFailures(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/api2/json/version":
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
fmt.Fprint(w, "unauthorized")
|
|
|
|
case "/api2/json/access/ticket":
|
|
t.Fatalf("token client should not request auth ticket")
|
|
|
|
default:
|
|
t.Fatalf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, err := pmg.NewClient(pmg.ClientConfig{
|
|
Host: server.URL,
|
|
User: "apitest@pmg",
|
|
TokenName: "apitoken",
|
|
TokenValue: "secret",
|
|
VerifySSL: false,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error creating token client: %v", err)
|
|
}
|
|
|
|
cfg := &config.Config{
|
|
PMGInstances: []config.PMGInstance{
|
|
{
|
|
Name: "failing",
|
|
Host: server.URL,
|
|
User: "apitest@pmg",
|
|
TokenName: "apitoken",
|
|
TokenValue: "secret",
|
|
},
|
|
},
|
|
}
|
|
|
|
mon, err := New(cfg)
|
|
if err != nil {
|
|
t.Fatalf("failed to create monitor: %v", err)
|
|
}
|
|
|
|
mon.pollPMGInstance(context.Background(), "failing", client)
|
|
|
|
snapshot := mon.state.GetSnapshot()
|
|
|
|
if health := snapshot.ConnectionHealth["pmg-failing"]; health {
|
|
t.Fatalf("expected unhealthy connection, got %v", health)
|
|
}
|
|
|
|
if len(snapshot.PMGInstances) != 1 {
|
|
t.Fatalf("expected failed PMG instance in state, got %d", len(snapshot.PMGInstances))
|
|
}
|
|
|
|
instance := snapshot.PMGInstances[0]
|
|
if instance.Status != "offline" {
|
|
t.Fatalf("expected offline status, got %s", instance.Status)
|
|
}
|
|
|
|
if instance.ConnectionHealth != "unhealthy" {
|
|
t.Fatalf("expected unhealthy connection status, got %s", instance.ConnectionHealth)
|
|
}
|
|
|
|
if failures := mon.authFailures["pmg-failing"]; failures != 1 {
|
|
t.Fatalf("expected one auth failure tracked, got %d", failures)
|
|
}
|
|
}
|