Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
99 lines
2.8 KiB
Go
99 lines
2.8 KiB
Go
package groups
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/v10networks/nmon/internal/clock"
|
|
"github.com/v10networks/nmon/internal/p95"
|
|
"github.com/v10networks/nmon/internal/p95/p95test"
|
|
"github.com/v10networks/nmon/internal/store"
|
|
"github.com/v10networks/nmon/internal/testutil"
|
|
"github.com/v10networks/nmon/internal/ulid"
|
|
)
|
|
|
|
var toronto = func() *time.Location {
|
|
l, err := time.LoadLocation("America/Toronto")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return l
|
|
}()
|
|
|
|
// rig is a real SQLite inventory, a fake long store and a fake clock.
|
|
type rig struct {
|
|
st store.Store
|
|
clk *clock.Fake
|
|
long *p95test.Long
|
|
alarms *p95test.Alarms
|
|
dev *store.Device
|
|
group store.Group
|
|
opts Options
|
|
}
|
|
|
|
func newRig(t *testing.T) *rig { return newRigAt(t, time.Date(2026, 5, 3, 0, 0, 0, 0, time.UTC)) }
|
|
|
|
func newRigAt(t *testing.T, now time.Time) *rig {
|
|
t.Helper()
|
|
clk := clock.NewFake(now)
|
|
st := testutil.OpenStore(t, clk)
|
|
dev := &store.Device{Name: "sw1", Kind: store.KindNetwork, Enabled: true}
|
|
must(t, st.CreateDevice(context.Background(), dev))
|
|
g := store.Group{Name: "Acme", Kind: store.GroupCustomer}
|
|
must(t, st.CreateGroup(context.Background(), &g))
|
|
long := p95test.NewLong()
|
|
r := &rig{st: st, clk: clk, long: long, alarms: &p95test.Alarms{}, dev: dev, group: g}
|
|
r.opts = Options{Query: long, Inventory: st, Zone: toronto, Clock: clk}
|
|
return r
|
|
}
|
|
|
|
func must(t *testing.T, err error) {
|
|
t.Helper()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (r *rig) svc() *Service { return NewService(r.opts) }
|
|
|
|
var ifaceSeq int64
|
|
|
|
func (r *rig) iface(t *testing.T, unreliable bool) string {
|
|
t.Helper()
|
|
ifaceSeq++
|
|
i := store.Interface{ID: ulid.New(r.clk.Now()), DeviceID: r.dev.ID, IfIndex: ifaceSeq, IfName: "e" + strconv.FormatInt(ifaceSeq, 10),
|
|
AdminStatus: 1, OperStatus: 1, HasHCCounters: !unreliable, Unreliable: unreliable}
|
|
must(t, r.st.UpsertInterfaces(context.Background(), r.dev.ID, []store.Interface{i}))
|
|
return i.ID
|
|
}
|
|
|
|
// member adds an open membership starting at from.
|
|
func (r *rig) member(t *testing.T, id, dir string, from time.Time) {
|
|
t.Helper()
|
|
must(t, r.st.AddGroupMember(context.Background(), store.GroupMember{
|
|
GroupID: r.group.ID, InterfaceID: id, Direction: dir, EffectiveFrom: from}))
|
|
}
|
|
|
|
// rates sets in/out for every window in [from, to); fn returns the pair for window i.
|
|
func (r *rig) rates(id string, from, to time.Time, fn func(i int) (in, out float64, ok bool)) {
|
|
for i, ts := 0, from; ts.Before(to); i, ts = i+1, ts.Add(p95.Window) {
|
|
if in, out, ok := fn(i); ok {
|
|
r.long.Set(id, p95.DirIn, ts, in)
|
|
r.long.Set(id, p95.DirOut, ts, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func constant(in, out float64) func(int) (float64, float64, bool) {
|
|
return func(int) (float64, float64, bool) { return in, out, true }
|
|
}
|
|
|
|
func month(t *testing.T, label string) p95.Month {
|
|
t.Helper()
|
|
m, err := p95.ParseMonth(label, toronto)
|
|
must(t, err)
|
|
return m
|
|
}
|