92 lines
2.7 KiB
Go
92 lines
2.7 KiB
Go
package audit
|
|
|
|
import (
|
|
"log/slog"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestNopRecorder covers the disabled-auditing no-op implementation.
|
|
func TestNopRecorder(t *testing.T) {
|
|
rec := Nop()
|
|
rec.Record(Event{Action: "x"}) // must not panic
|
|
got, err := rec.Query(Filter{})
|
|
if err != nil {
|
|
t.Fatalf("Nop Query: %v", err)
|
|
}
|
|
if len(got) != 0 {
|
|
t.Errorf("Nop Query returned %d events, want 0", len(got))
|
|
}
|
|
if err := rec.Close(); err != nil {
|
|
t.Errorf("Nop Close: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestQueryFilters covers the End, DS, and Signal (LIKE) filter branches plus
|
|
// the default-outcome and zero-time stamping in Record.
|
|
func TestQueryFilters(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "audit.db")
|
|
rec, err := NewSQLite(path, slog.Default())
|
|
if err != nil {
|
|
t.Fatal("NewSQLite:", err)
|
|
}
|
|
defer rec.Close()
|
|
|
|
// Anchor base in the past so the zero-Time 'c' record (stamped with now)
|
|
// sorts after a/b and stays out of the End window below.
|
|
base := time.Now().Add(-time.Hour)
|
|
rec.Record(Event{Time: base, Actor: "a", ActorType: ActorUser, Action: "signal.write", DS: "epics", Signal: "PV:TEMP"})
|
|
rec.Record(Event{Time: base.Add(time.Second), Actor: "b", ActorType: ActorUser, Action: "signal.write", DS: "synthetic", Signal: "PV:FLOW"})
|
|
// Zero Time and empty Outcome exercise the defaulting branches in Record.
|
|
rec.Record(Event{Actor: "c", ActorType: ActorUser, Action: "interface.update"})
|
|
|
|
if err := rec.Close(); err != nil {
|
|
t.Fatal("Close:", err)
|
|
}
|
|
rec, err = NewSQLite(path, slog.Default())
|
|
if err != nil {
|
|
t.Fatal("reopen:", err)
|
|
}
|
|
defer rec.Close()
|
|
|
|
// End filter: only events at or before base.
|
|
upTo, err := rec.Query(Filter{End: base.Add(500 * time.Millisecond)})
|
|
if err != nil {
|
|
t.Fatal("Query end:", err)
|
|
}
|
|
if len(upTo) != 1 || upTo[0].Actor != "a" {
|
|
t.Errorf("end filter = %+v, want one 'a' event", upTo)
|
|
}
|
|
|
|
// DS filter.
|
|
byDS, err := rec.Query(Filter{DS: "synthetic"})
|
|
if err != nil {
|
|
t.Fatal("Query ds:", err)
|
|
}
|
|
if len(byDS) != 1 || byDS[0].Signal != "PV:FLOW" {
|
|
t.Errorf("ds filter = %+v, want one PV:FLOW event", byDS)
|
|
}
|
|
|
|
// Signal LIKE filter (substring match).
|
|
bySignal, err := rec.Query(Filter{Signal: "TEMP"})
|
|
if err != nil {
|
|
t.Fatal("Query signal:", err)
|
|
}
|
|
if len(bySignal) != 1 || bySignal[0].DS != "epics" {
|
|
t.Errorf("signal filter = %+v, want one epics event", bySignal)
|
|
}
|
|
|
|
// The zero-time/empty-outcome record was persisted with a default outcome.
|
|
def, err := rec.Query(Filter{Actor: "c"})
|
|
if err != nil {
|
|
t.Fatal("Query actor c:", err)
|
|
}
|
|
if len(def) != 1 || def[0].Outcome != OutcomeOK {
|
|
t.Errorf("defaulted record = %+v, want one event with outcome ok", def)
|
|
}
|
|
if def[0].Time.IsZero() {
|
|
t.Error("zero Time should have been stamped with now")
|
|
}
|
|
}
|