stillbox/pkg/alerting/rules/alertrules_test.go

146 lines
3.3 KiB
Go

package rules_test
import (
"encoding/json"
"errors"
"math"
"testing"
"time"
"dynatron.me/x/stillbox/internal/ruletime"
"dynatron.me/x/stillbox/internal/trending"
"dynatron.me/x/stillbox/pkg/alerting/rules"
"dynatron.me/x/stillbox/pkg/talkgroups"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAlertConfig(t *testing.T) {
parseTests := []struct {
name string
tg talkgroups.ID
conf string
compare rules.AlertRules
expectErr error
}{
{
name: "base case",
tg: talkgroups.TG(197, 3),
conf: `[{"times":["7:00+2h","01:00+1h","16:00+1h","19:00+4h"],"mult":0.2},{"times":["11:00+1h","15:00+30m","16:03+20m"],"mult":2.0}]`,
compare: rules.AlertRules{
{
Times: []ruletime.RuleTime{
ruletime.Must(ruletime.New("7:00+2h")),
ruletime.Must(ruletime.New("1:00+1h")),
ruletime.Must(ruletime.New("16:00+1h")),
ruletime.Must(ruletime.New("19:00+4h")),
},
ScoreMultiplier: 0.2,
},
{
Times: []ruletime.RuleTime{
ruletime.Must(ruletime.New("11:00+1h")),
ruletime.Must(ruletime.New("15:00+30m")),
ruletime.Must(ruletime.New("16:03+20m")),
},
ScoreMultiplier: 2.0,
},
},
},
{
name: "bad spec",
tg: talkgroups.TG(197, 3),
conf: `[{"times":["26:00+2h","01:00+1h","19:00+4h"],"mult":0.2},{"times":["11:00+1h","15:00+30m"],"mult":2.0}]`,
expectErr: errors.New("'26:00+2h': invalid hours"),
},
}
tgc := make(map[talkgroups.ID]rules.AlertRules)
for _, tc := range parseTests {
t.Run(tc.name, func(t *testing.T) {
var ar rules.AlertRules
err := json.Unmarshal([]byte(tc.conf), &ar)
if tc.expectErr != nil {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.expectErr.Error())
} else {
tgc[tc.tg] = ar
assert.Equal(t, tc.compare, ar)
}
})
}
tMust := func(s string) time.Time {
t, err := time.ParseInLocation("2006-01-02 15:04", "2024-11-02 "+s, time.Local)
if err != nil {
panic(err)
}
return t
}
evalTests := []struct {
name string
tg talkgroups.ID
t time.Time
origScore float64
expectScore float64
}{
{
name: "base eval",
tg: talkgroups.TG(197, 3),
t: tMust("1:20"),
origScore: 3,
expectScore: 0.6,
},
{
name: "base eval",
tg: talkgroups.TG(197, 3),
t: tMust("23:03"),
origScore: 3,
expectScore: 3,
},
{
name: "base eval",
tg: talkgroups.TG(197, 3),
t: tMust("8:03"),
origScore: 1.0,
expectScore: 0.2,
},
{
name: "base eval",
tg: talkgroups.TG(197, 3),
t: tMust("15:15"),
origScore: 3.0,
expectScore: 6.0,
},
{
name: "overlapping eval",
tg: talkgroups.TG(197, 3),
t: tMust("16:10"),
origScore: 1.0,
expectScore: 0.4,
},
}
for _, tc := range evalTests {
t.Run(tc.name, func(t *testing.T) {
cs := trending.Score[talkgroups.ID]{
ID: tc.tg,
Score: tc.origScore,
}
assert.Equal(t, tc.expectScore, toFixed(cs.Score*tgc[cs.ID].Apply(tc.t), 5))
})
}
}
func round(num float64) int {
return int(num + math.Copysign(0.5, num))
}
func toFixed(num float64, precision int) float64 {
output := math.Pow(10, float64(precision))
return float64(round(num*output)) / output
}