Skip to content
quarry Artifact [060b557b86]
Login

Artifact [060b557b86]

Artifact 060b557b86e420d16790098025fe4c0ad2abcd4a172e41fc67cc9df21bbf9324:


//go:build !js && !wasip1

package obsduck

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

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

// A hit lands with every fact it was filed with, and its address is
// blanked once it is a day old while the row itself stays.
func TestHitsLandAndTheirAddressesGoAfterADay(t *testing.T) {
	s, err := Open(filepath.Join(t.TempDir(), "ops.duckdb"), 4)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()
	old := time.Now().Add(-AddrKept - time.Hour)
	s.Hit(obs.Hit{At: old, Door: obs.DoorSync, URL: "/r/quarry", Repo: "quarry", Addr: "203.0.113.9",
		Country: "DE", UA: "zit/0.6.6", Verb: "clone", Client: "zit", Bytes: 4096})
	s.Hit(obs.Hit{At: time.Now(), Door: obs.DoorWeb, URL: "/r/quarry/timeline", Addr: "198.51.100.7"})
	if _, err := s.Prune(14 * 24 * time.Hour); err != nil {
		t.Fatal(err)
	}
	rows, err := s.db.Query(`SELECT door, verb, country, bytes, coalesce(addr, '') FROM hit ORDER BY ts`)
	if err != nil {
		t.Fatal(err)
	}
	defer rows.Close()
	var got [][5]string
	for rows.Next() {
		var r [5]string
		if err := rows.Scan(&r[0], &r[1], &r[2], &r[3], &r[4]); err != nil {
			t.Fatal(err)
		}
		got = append(got, r)
	}
	if len(got) != 2 {
		t.Fatalf("FAIL: %d rows, want 2", len(got))
	}
	if got[0] != [5]string{"sync", "clone", "DE", "4096", ""} {
		t.Errorf("FAIL: the day-old hit reads %v: its address should be gone and the rest kept", got[0])
	}
	if got[1][4] != "198.51.100.7" {
		t.Errorf("FAIL: a fresh hit lost its address: %v", got[1])
	}
}

// The traffic page's rows outlive a shorter window for the rest: its
// longest view is a month.
func TestHitsAreKeptForTheMonthView(t *testing.T) {
	s, err := Open(filepath.Join(t.TempDir(), "ops.duckdb"), 4)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()
	at := time.Now().Add(-20 * 24 * time.Hour)
	s.Hit(obs.Hit{At: at, Door: obs.DoorWeb})
	s.Render(obs.Render{At: at, Path: "/"})
	if _, err := s.Prune(14 * 24 * time.Hour); err != nil {
		t.Fatal(err)
	}
	var hits, renders int
	s.db.QueryRow(`SELECT count(*) FROM hit`).Scan(&hits)
	s.db.QueryRow(`SELECT count(*) FROM render`).Scan(&renders)
	if hits != 1 || renders != 0 {
		t.Errorf("FAIL: after a 14-day prune, %d hits and %d renders of 20 days ago remain; want 1 and 0", hits, renders)
	}
}

// A visitor key is any value the recorder makes, and a batch with one in
// it still lands — the renders beside it included.
func TestAnyVisitorKeyLands(t *testing.T) {
	s, err := Open(filepath.Join(t.TempDir(), "ops.duckdb"), 1024)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()
	rec := obs.NewRecorder(4, s)
	for i := 0; i < 64; i++ {
		rec.RecordHit(obs.Hit{At: time.Now(), Door: obs.DoorWeb, Addr: fmt.Sprintf("203.0.113.%d", i), UA: "x"})
	}
	s.Render(obs.Render{At: time.Now(), Path: "/"})
	s.Flush()
	if err := s.Err(); err != nil {
		t.Fatalf("FAIL: the batch did not land: %v", err)
	}
	var hits, renders int
	s.db.QueryRow(`SELECT count(*) FROM hit`).Scan(&hits)
	s.db.QueryRow(`SELECT count(*) FROM render`).Scan(&renders)
	if hits != 64 || renders != 1 {
		t.Errorf("FAIL: %d hits and %d renders landed, want 64 and 1", hits, renders)
	}
}