forage_test.go raw

   1  package forage
   2  
   3  import (
   4  	"testing"
   5  
   6  	"git.mleku.dev/mleku/dendrite/pkg/gap"
   7  	"git.mleku.dev/mleku/dendrite/pkg/ratio"
   8  )
   9  
  10  func TestAppetite(t *testing.T) {
  11  	gaps := []gap.Gap{
  12  		{Type: "missing_site", Tag: "hashtag", Severity: ratio.Half, Count: 10},
  13  		{Type: "orphan_event", Tag: "event", Severity: ratio.New(4, 5), Count: 20},
  14  		{Type: "low_occupancy", Tag: "occupancy", Severity: ratio.New(7, 10), Count: 50},
  15  	}
  16  
  17  	needs := Appetite(gaps)
  18  	if len(needs) != 3 {
  19  		t.Fatalf("expected 3 needs, got %d", len(needs))
  20  	}
  21  
  22  	// Highest priority first.
  23  	if needs[0].Priority.Less(needs[1].Priority) {
  24  		t.Error("needs should be sorted by priority descending")
  25  	}
  26  
  27  	// Check need types.
  28  	if needs[0].Type != NeedSpecification { // orphan_event → specification
  29  		t.Errorf("expected specification need for orphan, got %s", needs[0].Type)
  30  	}
  31  }
  32  
  33  func TestURLForNeed(t *testing.T) {
  34  	tests := []struct {
  35  		need Need
  36  		want string
  37  	}{
  38  		{
  39  			need: Need{Topic: "NIP-42"},
  40  			want: "https://raw.githubusercontent.com/nostr-protocol/nips/master/42.md",
  41  		},
  42  		{
  43  			need: Need{Topic: "encoding/json"},
  44  			want: "https://pkg.go.dev/encoding/json",
  45  		},
  46  		{
  47  			need: Need{Topic: "how to improve occupancy"},
  48  			want: "", // no known URL pattern
  49  		},
  50  	}
  51  
  52  	for _, tt := range tests {
  53  		got := URLForNeed(tt.need)
  54  		if got != tt.want {
  55  			t.Errorf("URLForNeed(%q) = %q, want %q", tt.need.Topic, got, tt.want)
  56  		}
  57  	}
  58  }
  59  
  60  func TestExtractDomain(t *testing.T) {
  61  	tests := []struct {
  62  		url  string
  63  		want string
  64  	}{
  65  		{"https://go.dev/doc/effective_go", "go.dev"},
  66  		{"https://pkg.go.dev/encoding/json", "pkg.go.dev"},
  67  		{"https://github.com:443/nostr/nips", "github.com"},
  68  		{"http://localhost:8080/path", "localhost"},
  69  	}
  70  
  71  	for _, tt := range tests {
  72  		got := extractDomain(tt.url)
  73  		if got != tt.want {
  74  			t.Errorf("extractDomain(%q) = %q, want %q", tt.url, got, tt.want)
  75  		}
  76  	}
  77  }
  78  
  79  func TestDomainWhitelist(t *testing.T) {
  80  	f := NewForager()
  81  
  82  	if !f.DomainWhitelist["go.dev"] {
  83  		t.Error("go.dev should be whitelisted")
  84  	}
  85  	if f.DomainWhitelist["evil.com"] {
  86  		t.Error("evil.com should not be whitelisted")
  87  	}
  88  }
  89  
  90  func TestDigestMarkdown(t *testing.T) {
  91  	md := []byte(`# NIP-42: Authentication
  92  
  93  Relays may require authentication.
  94  
  95  ` + "```go" + `
  96  func handleAuth(ev *Event) {
  97      // verify signature
  98  }
  99  ` + "```" + `
 100  
 101  Create a challenge and send it to the client.
 102  `)
 103  
 104  	elems := digestMarkdown(md)
 105  	if len(elems) == 0 {
 106  		t.Fatal("expected elements from markdown")
 107  	}
 108  
 109  	types := make(map[string]int)
 110  	for _, e := range elems {
 111  		types[e.Type()]++
 112  	}
 113  
 114  	if types["heading"] == 0 {
 115  		t.Error("expected heading elements")
 116  	}
 117  	if types["code"] == 0 {
 118  		t.Error("expected code elements")
 119  	}
 120  	if types["word"] == 0 {
 121  		t.Error("expected word elements")
 122  	}
 123  }
 124  
 125  func TestDigestGoSource(t *testing.T) {
 126  	src := []byte(`package main
 127  
 128  import "fmt"
 129  
 130  type Relay struct {
 131      Name string
 132  }
 133  
 134  func (r *Relay) Start() {
 135      fmt.Println("starting")
 136  }
 137  `)
 138  
 139  	elems := digestGoSource(src)
 140  	types := make(map[string]int)
 141  	for _, e := range elems {
 142  		types[e.Type()]++
 143  	}
 144  
 145  	if types["package"] == 0 {
 146  		t.Error("expected package element")
 147  	}
 148  	if types["import"] == 0 {
 149  		t.Error("expected import element")
 150  	}
 151  	if types["type"] == 0 {
 152  		t.Error("expected type element")
 153  	}
 154  	if types["func"] == 0 {
 155  		t.Error("expected func element")
 156  	}
 157  }
 158  
 159  func TestDigestHTML(t *testing.T) {
 160  	html := []byte(`<html><body><h1>Title</h1><p>Some text here with <b>bold</b> words.</p></body></html>`)
 161  	elems := digestHTML(html)
 162  	if len(elems) == 0 {
 163  		t.Fatal("expected elements from HTML")
 164  	}
 165  }
 166  
 167  func TestStripHTMLTags(t *testing.T) {
 168  	html := `<p>Hello <b>world</b></p>`
 169  	got := stripHTMLTags(html)
 170  	if got != " Hello  world  " {
 171  		t.Errorf("stripHTMLTags = %q", got)
 172  	}
 173  }
 174  
 175  func TestBandwidthCap(t *testing.T) {
 176  	f := NewForager()
 177  	f.MaxBytesPerGen = 100
 178  	f.SetGeneration(0)
 179  
 180  	if f.BytesRemaining() != 100 {
 181  		t.Errorf("expected 100 bytes remaining, got %d", f.BytesRemaining())
 182  	}
 183  
 184  	// Simulate usage.
 185  	f.mu.Lock()
 186  	f.bytesUsed = 80
 187  	f.mu.Unlock()
 188  
 189  	if f.BytesRemaining() != 20 {
 190  		t.Errorf("expected 20 bytes remaining, got %d", f.BytesRemaining())
 191  	}
 192  }
 193  
 194  func TestFilterUsefulSources(t *testing.T) {
 195  	records := []SourceRecord{
 196  		{URL: "https://go.dev/a", BondRatio: 0.8},
 197  		{URL: "https://go.dev/b", BondRatio: 0.1},
 198  		{URL: "https://go.dev/c", BondRatio: 0.5},
 199  	}
 200  
 201  	useful := FilterUsefulSources(records, 0.3)
 202  	if len(useful) != 2 {
 203  		t.Errorf("expected 2 useful sources, got %d", len(useful))
 204  	}
 205  }
 206  
 207  func TestDigestRoutesCorrectly(t *testing.T) {
 208  	// Markdown by URL suffix.
 209  	md := &FetchResult{
 210  		URL:        "https://example.com/doc.md",
 211  		Body:       []byte("# Hello\n\nWorld"),
 212  		StatusCode: 200,
 213  	}
 214  	elems := Digest(md)
 215  	if len(elems) == 0 {
 216  		t.Error("expected elements from .md URL")
 217  	}
 218  
 219  	// Go source.
 220  	goSrc := &FetchResult{
 221  		URL:        "https://example.com/main.go",
 222  		Body:       []byte("package main\n\nfunc main() {}"),
 223  		StatusCode: 200,
 224  	}
 225  	elems = Digest(goSrc)
 226  	hasFunc := false
 227  	for _, e := range elems {
 228  		if e.Type() == "func" {
 229  			hasFunc = true
 230  		}
 231  	}
 232  	if !hasFunc {
 233  		t.Error("expected func element from .go source")
 234  	}
 235  
 236  	// Failed fetch should return nil.
 237  	failed := &FetchResult{
 238  		URL:        "https://example.com/404",
 239  		StatusCode: 404,
 240  	}
 241  	if Digest(failed) != nil {
 242  		t.Error("expected nil from failed fetch")
 243  	}
 244  }
 245