Skip to content
quarry Artifact [fcacb5b8b0]
Login

Artifact [fcacb5b8b0]

Artifact fcacb5b8b039d4798ea98f0afd79c7d75d7cc7dd5be55df56629135ceaa3f982:


//go:build !js && !wasip1

package obsduck

import (
	"path/filepath"
	"strings"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/obs"
)

func trafficSink(t *testing.T) (*Sink, time.Time) {
	t.Helper()
	s, err := Open(filepath.Join(t.TempDir(), "ops.duckdb"), 1024)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { s.Close() })
	now := time.Date(2026, 10, 23, 14, 10, 0, 0, time.UTC)
	at := now.Add(-3 * time.Hour)
	firefox := "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0"
	googlebot := "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
	add := func(h obs.Hit) {
		if h.At.IsZero() {
			h.At = at
		}
		if h.Status == 0 {
			h.Status = 200
		}
		s.Hit(h)
	}
	// A person reading pages, from a home network.
	for i := 0; i < 5; i++ {
		add(obs.Hit{Door: obs.DoorWeb, URL: "/r/quarry/timeline", Repo: "quarry", UA: firefox,
			ASN: 7922, Network: "Comcast", Country: "US", Visitor: 1, Elapsed: 40 * time.Millisecond,
			Referrer: "news.ycombinator.com"})
	}
	// Googlebot, real and forged.
	add(obs.Hit{Door: obs.DoorWeb, URL: "/r/quarry", Repo: "quarry", UA: googlebot, ASN: 15169, Network: "Google", Visitor: 2})
	for i := 0; i < 3; i++ {
		add(obs.Hit{Door: obs.DoorWeb, URL: "/r/pkgsrc/file/x/y", Repo: "pkgsrc", UA: googlebot,
			ASN: 45102, Network: "Alibaba Cloud", Country: "SG", Visitor: 3, Elapsed: 900 * time.Millisecond, Bytes: 1000})
	}
	// A signed-in clone, and a mirror polling every minute for a day.
	add(obs.Hit{Door: obs.DoorSync, URL: "/r/quarry", Repo: "quarry", UA: "zit/0.6.6", Verb: "clone",
		Client: "zit", Account: "dmestas", ASN: 7922, Network: "Comcast", Visitor: 4, Bytes: 5000})
	for i := 0; i < 30; i++ {
		add(obs.Hit{At: now.Add(-20*time.Hour + time.Duration(i)*40*time.Minute), Door: obs.DoorSync,
			URL: "/r/quarry", Repo: "quarry", UA: "zit/0.6.6", Verb: "fetch", Client: "zit",
			ASN: 24940, Network: "Hetzner", Visitor: 5, Bytes: 100})
	}
	// A script fetching an archive; a server error.
	add(obs.Hit{Door: obs.DoorWeb, URL: "/r/zit/archive/trunk", Repo: "zit", UA: "curl/8.4.0", Visitor: 6, Bytes: 9000})
	add(obs.Hit{Door: obs.DoorWeb, URL: "/r/quarry/ticket/1", Repo: "quarry", UA: firefox, ASN: 7922, Status: 500, Visitor: 1})
	s.Flush()
	return s, now
}

func TestTrafficReadsTheLogBack(t *testing.T) {
	s, now := trafficSink(t)
	tr, err := s.Traffic(obs.TrafficQuery{Range: obs.RangeNamed("24h"), Now: now})
	if err != nil {
		t.Fatal(err)
	}
	if tr.Totals.Requests != 42 || tr.Totals.Errors != 1 || tr.Totals.Visitors != 6 {
		t.Errorf("FAIL: totals %+v", tr.Totals)
	}
	if want := int(tr.Range.Span / tr.Range.Step); len(tr.Points) != want {
		t.Errorf("FAIL: %d points for a day, want %d", len(tr.Points), want)
	}
	var charted int64
	for _, p := range tr.Points {
		for _, n := range p.ByKind {
			charted += n
		}
	}
	if charted != tr.Totals.Requests {
		t.Errorf("FAIL: the chart holds %d requests of %d", charted, tr.Totals.Requests)
	}
	if len(tr.Crawlers) < 2 || !tr.Crawlers[0].Flag || !strings.Contains(tr.Crawlers[0].Label, "Googlebot") {
		t.Errorf("FAIL: the impostor does not lead the crawlers: %+v", tr.Crawlers)
	}
	if len(tr.Referrers) != 1 || tr.Referrers[0].Label != "news.ycombinator.com" {
		t.Errorf("FAIL: referrers %+v", tr.Referrers)
	}
	if len(tr.Downloads) == 0 || tr.Downloads[0].Repo != "quarry" || tr.Downloads[0].Clones != 1 ||
		tr.Downloads[0].Fetches != 30 || tr.Downloads[0].SignedIn != 1 || tr.Downloads[0].Zit != 31 {
		t.Errorf("FAIL: downloads %+v", tr.Downloads)
	}
	var mirror, signed bool
	for _, src := range tr.Sources {
		if strings.Contains(src.Why, "every") && strings.Contains(src.Name, "Hetzner") {
			mirror = true
		}
		if src.Name == "dmestas" && src.Kind == obs.KindPerson {
			signed = true
		}
	}
	if !mirror || !signed {
		t.Errorf("FAIL: sources %+v", tr.Sources)
	}
	if tr.P95 < 900 {
		t.Errorf("FAIL: p95 %d misses the slow crawl", tr.P95)
	}
}

// A filter narrows every panel, the totals with them.
func TestATrafficFilterNarrowsEverything(t *testing.T) {
	s, now := trafficSink(t)
	tr, err := s.Traffic(obs.TrafficQuery{Range: obs.RangeNamed("24h"), Now: now,
		Filters: map[string]string{"network": "45102"}})
	if err != nil {
		t.Fatal(err)
	}
	if tr.Totals.Requests != 3 || len(tr.Downloads) != 0 || len(tr.Networks) != 1 {
		t.Errorf("FAIL: filtered to Alibaba Cloud, read %d requests, %d downloads, %d networks",
			tr.Totals.Requests, len(tr.Downloads), len(tr.Networks))
	}
}

// A week of many distinct clients classifies in batches, every one of
// them, and in time: one INSERT per fact made the 7-day page time out.
func TestAWeekOfManyClientsIsClassifiedWhole(t *testing.T) {
	s, err := Open(filepath.Join(t.TempDir(), "ops.duckdb"), 1024)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { s.Close() })
	now := time.Date(2026, 10, 23, 14, 10, 0, 0, time.UTC)
	const n = 1234 // more than two batches, not a multiple of one
	for i := 0; i < n; i++ {
		s.Hit(obs.Hit{At: now.Add(-time.Duration(i) * time.Minute), Door: obs.DoorWeb, URL: "/r/quarry",
			Repo: "quarry", Status: 200, UA: "agent/" + strings.Repeat("x", i%7) + time.Duration(i).String(), Visitor: uint64(i + 1)})
		if i%200 == 199 {
			s.Flush() // the sink's buffer holds 1,024
		}
	}
	s.Flush()
	start := time.Now()
	tr, err := s.Traffic(obs.TrafficQuery{Range: obs.RangeNamed("7d"), Now: now})
	if err != nil {
		t.Fatal(err)
	}
	if tr.Totals.Requests != n {
		t.Errorf("FAIL: %d requests counted, want %d: a batch lost its facts", tr.Totals.Requests, n)
	}
	t.Logf("ok: %d distinct clients over 7d in %v", n, time.Since(start))
}