package watch import ( "net/http/httptest" "path/filepath" "testing" "github.com/david/knox/internal/db" ) func tmpKnoxDB(t *testing.T) *db.KnoxDB { t.Helper() kdb, err := db.Open(filepath.Join(t.TempDir(), "index.db")) if err != nil { t.Fatalf("open db: %v", err) } t.Cleanup(func() { kdb.Close() }) return kdb } func seedObs(k *db.KnoxDB, prefix string) { // Insert distinct observations only via RecordObservation so they get this // node's node_id and HCL. for i := 0; i < 3; i++ { _, _, err := k.RecordObservation(db.ObservationRecord{ Fingerprint: prefix + ":" + string(rune('a'+i)), SourceID: "test", SourcePath: prefix, Project: "itest", ContentType: "test", Title: prefix + string(rune('a'+i)), Summary: "summary " + prefix + string(rune('a'+i)), CreatedAt: "2026-08-29T00:00:00Z", LineEnd: 0, Confidence: 0.9, IngesterVersion: "itest/v1", }) if err != nil { panic(err) } } } // TestGossipConvergence starts two nodes, seeds disjoint observations, and runs // a bidirectional handshake. Both must end with the concatenation of both logs. func TestGossipConvergence(t *testing.T) { a := tmpKnoxDB(t) b := tmpKnoxDB(t) seedObs(a, "AAA") seedObs(b, "BBB") // Wire a and b as peer HTTP servers. nodeA := &Node{Kdb: a, Name: "A"} sa := httptest.NewServer(nodeA.Handler()) defer sa.Close() nodeB := &Node{Kdb: b, Name: "B"} sb := httptest.NewServer(nodeB.Handler()) defer sb.Close() // A pulls from B, then B pulls from A (bidirectional sweep). Run(a, []string{sb.URL}) Run(b, []string{sa.URL}) av, err := a.KnowledgeVector() if err != nil { t.Fatalf("a vector: %v", err) } bv, err := b.KnowledgeVector() if err != nil { t.Fatalf("b vector: %v", err) } if len(av) != 2 || len(bv) != 2 { t.Fatalf("expected both nodes to hold 2 source logs, got a=%v b=%v", av, bv) } // Same maximum HCL per origin node on both sides. for nid, hcl := range av { if bv[nid] != hcl { t.Errorf("node %s max hcl mismatch: a=%d b=%d", nid, hcl, bv[nid]) } } } // TestGossipIdempotent ensures a repeated sweep does not duplicate observations. func TestGossipIdempotent(t *testing.T) { a := tmpKnoxDB(t) b := tmpKnoxDB(t) seedObs(a, "AAA") nodeA := &Node{Kdb: a, Name: "A"} sa := httptest.NewServer(nodeA.Handler()) defer sa.Close() nodeB := &Node{Kdb: b, Name: "B"} sb := httptest.NewServer(nodeB.Handler()) defer sb.Close() Run(b, []string{sa.URL}) before, err := b.EntryCount() if err != nil { t.Fatal(err) } Run(b, []string{sa.URL}) after, err := b.EntryCount() if err != nil { t.Fatal(err) } if before != after { t.Errorf("second sweep changed entry count: %d -> %d", before, after) } }