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") } }