Files
nmon/internal/store/groups_test.go

63 lines
2.4 KiB
Go

package store
import (
"context"
"errors"
"testing"
"time"
)
func TestGroupMembershipHistoryIsAppendOnlyAndNeverOverlaps(t *testing.T) {
run(t, func(t *testing.T, e *env) {
ctx := context.Background()
d := newDevice(t, e, "A")
i1 := viewIface(t, e, d, 1, "e1", "", nil)
i2 := viewIface(t, e, d, 2, "e2", "", nil)
g := &Group{Name: "Acme", Kind: GroupCustomer}
must(t, e.CreateGroup(ctx, g))
if err := e.CreateGroup(ctx, &Group{Name: "Acme", Kind: GroupCustomer}); !errors.Is(err, ErrConflict) {
t.Fatalf("duplicate name = %v", err)
}
if err := e.CreateGroup(ctx, &Group{Name: "x", Kind: "bogus"}); !errors.Is(err, ErrInvalid) {
t.Fatalf("bad kind = %v", err)
}
jan := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
feb := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
must(t, e.AddGroupMember(ctx, GroupMember{GroupID: g.ID, InterfaceID: i1.ID, Direction: DirToward, EffectiveFrom: jan}))
must(t, e.AddGroupMember(ctx, GroupMember{GroupID: g.ID, InterfaceID: i2.ID, Direction: DirAway, EffectiveFrom: feb}))
err := e.AddGroupMember(ctx, GroupMember{GroupID: g.ID, InterfaceID: i1.ID, Direction: DirAway, EffectiveFrom: feb})
if !errors.Is(err, ErrConflict) {
t.Fatalf("overlapping add = %v", err)
}
if err := e.AddGroupMember(ctx, GroupMember{GroupID: g.ID, InterfaceID: "nope", Direction: DirAway}); !errors.Is(err, ErrInvalid) {
t.Fatalf("unknown interface = %v", err)
}
must(t, e.EndGroupMember(ctx, g.ID, i1.ID, feb.Add(24*time.Hour)))
// Re-adding with the other direction after the end keeps the old row.
must(t, e.AddGroupMember(ctx, GroupMember{GroupID: g.ID, InterfaceID: i1.ID, Direction: DirAway, EffectiveFrom: feb.Add(24 * time.Hour)}))
inJan, err := e.GroupMembers(ctx, g.ID, jan, feb)
must(t, err)
if len(inJan) != 1 || inJan[0].InterfaceID != i1.ID || inJan[0].Direction != DirToward {
t.Fatalf("January membership = %+v", inJan)
}
all, err := e.GroupMembers(ctx, 0, time.Time{}, time.Time{})
must(t, err)
if len(all) != 3 {
t.Fatalf("history = %+v", all)
}
if err := e.EndGroupMember(ctx, g.ID, i2.ID, feb); !errors.Is(err, ErrInvalid) {
t.Fatalf("end at start = %v", err)
}
must(t, e.DeleteGroup(ctx, g.ID))
if err := e.DeleteGroup(ctx, g.ID); !errors.Is(err, ErrNotFound) {
t.Fatalf("second delete = %v", err)
}
left, err := e.GroupMembers(ctx, 0, time.Time{}, time.Time{})
must(t, err)
if len(left) != 0 {
t.Fatalf("members survived delete: %+v", left)
}
})
}