package stats import ( "context" "errors" "io" "log/slog" "os" "path/filepath" "testing" "time" ) // Figures for chosen views: from the response log, per view, and added // up for several. func TestViewFilter(t *testing.T) { ctx := context.Background() loc := time.FixedZone("test", 0) dir := t.TempDir() logFile := filepath.Join(dir, "responses.log") store, err := OpenStore(filepath.Join(dir, "stats.db")) if err != nil { t.Fatal(err) } defer store.Close() named := newFakeNamed(t) clk := &clock{time.Date(2026, 10, 5, 10, 0, 30, 0, loc)} opts := Options{QueryLog: true, LogFile: logFile, StatisticsChannel: "unused", TopN: 10, RetainMinute: 7 * 24 * time.Hour, RetainHour: 90 * 24 * time.Hour, RetainDay: 730 * 24 * time.Hour, Location: loc} zones := func() map[string][]string { return map[string][]string{"internal": {"adbraun.lol"}, "external": {"adbraun.lol"}} } c := NewCollector(store, opts, zones, slog.New(slog.NewTextHandler(io.Discard, nil))) c.poller.URL = named.srv.URL c.now = clk.now c.start(ctx) at := time.Date(2026, 10, 5, 10, 0, 40, 0, loc) lines := []string{ logLine(at, "10.10.0.37", "internal", "nas.adbraun.lol", "A", "NOERROR"), logLine(at, "10.10.0.37", "internal", "nas.adbraun.lol", "AAAA", "NOERROR"), logLine(at, "10.10.0.42", "internal", "nope.adbraun.lol", "A", "NXDOMAIN"), logLine(at, "203.0.113.9", "external", "www.adbraun.lol", "A", "NOERROR"), logLine(at, "203.0.113.9", "external", "x.example", "A", "REFUSED"), logLine(at, "192.0.2.5", "vps", "debian.org", "AAAA", "NOERROR"), } f, _ := os.Create(logFile) for _, l := range lines { f.WriteString(l) } f.Close() c.readLog() named.add(6, 1) clk.t = time.Date(2026, 10, 5, 10, 1, 2, 0, loc) c.flush(ctx, false) clk.t = time.Date(2026, 10, 5, 11, 0, 2, 0, loc) c.flush(ctx, false) all := &Querier{Store: store, Opts: opts, Now: clk.now} s, err := all.Summary(ctx, 24*time.Hour, 5) if err != nil || s.Queries != 6 || s.FilterViews != nil { t.Fatalf("all views: %+v %v", s, err) } in := all.ForViews([]string{"internal"}) s, err = in.Summary(ctx, 24*time.Hour, 5) if err != nil { t.Fatal(err) } if s.Queries != 3 || len(s.Views) != 1 || s.Views[0].Key != "internal" || s.ViewsSince == nil { t.Errorf("internal: queries %d views %v since %v", s.Queries, s.Views, s.ViewsSince) } if len(s.RCodes) != 2 || s.RCodes[0] != (Entry{Key: "NOERROR", Count: 2}) || len(s.QTypes) != 2 { t.Errorf("internal rcodes %v qtypes %v", s.RCodes, s.QTypes) } if s.Tops[KindName][0] != (Entry{Key: "nas.adbraun.lol", Count: 2}) || len(s.Tops[KindNXDomain]) != 1 || len(s.Answers) != 0 { t.Errorf("internal tops %v answers %v", s.Tops, s.Answers) } // Linked views, say: two together. two := all.ForViews([]string{"internal", "external"}) s, _ = two.Summary(ctx, 24*time.Hour, 5) if s.Queries != 5 || len(s.Views) != 2 { t.Errorf("internal+external: %d %v", s.Queries, s.Views) } if es, _ := two.Top(ctx, KindClient, 24*time.Hour, 5); len(es) != 3 || es[2].Key != "10.10.0.42" { t.Errorf("clients of two views: %v", es) } if es, _ := two.Breakdown(ctx, "rcode", 24*time.Hour); len(es) != 3 { t.Errorf("rcodes of two views: %v", es) } if _, err := two.Breakdown(ctx, "answer", 24*time.Hour); !errors.Is(err, ErrNotPerView) { t.Errorf("answers per view: %v", err) } tl, err := all.ForViews([]string{"vps"}).Timeline(ctx, 2*time.Hour, "1m", []string{"queries", "rcode:*"}) if err != nil { t.Fatal(err) } var total int64 for _, v := range tl.Series[0].Values { total += v } if total != 1 || len(tl.Series) != 2 || tl.Series[1].Key != "rcode:NOERROR" { t.Errorf("vps timeline: %d %+v", total, tl.Series) } }