Files
next.orly.dev/pkg/spider/directory_test.go
2025-11-27 00:02:14 +00:00

167 lines
4.1 KiB
Go

package spider
import (
"context"
"testing"
"time"
"git.mleku.dev/mleku/nostr/encoders/event"
"git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/encoders/tag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestExtractRelaysFromEvents(t *testing.T) {
ds := &DirectorySpider{}
tests := []struct {
name string
events []*event.E
expected []string
}{
{
name: "empty events",
events: []*event.E{},
expected: []string{},
},
{
name: "single event with relays",
events: []*event.E{
{
Kind: kind.RelayListMetadata.K,
Tags: &tag.S{
tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay1.example.com")),
tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay2.example.com")),
},
},
},
expected: []string{"wss://relay1.example.com", "wss://relay2.example.com"},
},
{
name: "multiple events with duplicate relays",
events: []*event.E{
{
Kind: kind.RelayListMetadata.K,
Tags: &tag.S{
tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay1.example.com")),
},
},
{
Kind: kind.RelayListMetadata.K,
Tags: &tag.S{
tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay1.example.com")),
tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay3.example.com")),
},
},
},
expected: []string{"wss://relay1.example.com", "wss://relay3.example.com"},
},
{
name: "event with empty r tags",
events: []*event.E{
{
Kind: kind.RelayListMetadata.K,
Tags: &tag.S{
tag.NewFromBytesSlice([]byte("r")), // empty value
tag.NewFromBytesSlice([]byte("r"), []byte("wss://valid.relay.com")),
},
},
},
expected: []string{"wss://valid.relay.com"},
},
{
name: "normalizes relay URLs",
events: []*event.E{
{
Kind: kind.RelayListMetadata.K,
Tags: &tag.S{
tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay.example.com")),
tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay.example.com/")), // duplicate with trailing slash
},
},
},
expected: []string{"wss://relay.example.com"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ds.extractRelaysFromEvents(tt.events)
// For empty case, check length
if len(tt.expected) == 0 {
assert.Empty(t, result)
return
}
// Check that all expected relays are present (order may vary)
assert.Len(t, result, len(tt.expected))
for _, expected := range tt.expected {
assert.Contains(t, result, expected)
}
})
}
}
func TestDirectorySpiderLifecycle(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create spider without database (will return error)
_, err := NewDirectorySpider(ctx, nil, nil, 0, 0)
require.Error(t, err)
assert.Contains(t, err.Error(), "database cannot be nil")
}
func TestDirectorySpiderDefaults(t *testing.T) {
// Test that defaults are applied correctly
assert.Equal(t, 24*time.Hour, DirectorySpiderDefaultInterval)
assert.Equal(t, 3, DirectorySpiderDefaultMaxHops)
assert.Equal(t, 30*time.Second, DirectorySpiderRelayTimeout)
assert.Equal(t, 60*time.Second, DirectorySpiderQueryTimeout)
assert.Equal(t, 500*time.Millisecond, DirectorySpiderRelayDelay)
assert.Equal(t, 5000, DirectorySpiderMaxEventsPerQuery)
}
func TestTriggerNow(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ds := &DirectorySpider{
ctx: ctx,
triggerChan: make(chan struct{}, 1),
}
// First trigger should succeed
ds.TriggerNow()
// Verify trigger was sent
select {
case <-ds.triggerChan:
// Expected
default:
t.Error("trigger was not sent")
}
// Second trigger while channel is empty should also succeed
ds.TriggerNow()
// But if we trigger again without draining, it should not block
ds.TriggerNow() // Should not block due to select default case
}
func TestLastRun(t *testing.T) {
ds := &DirectorySpider{}
// Initially should be zero
assert.True(t, ds.LastRun().IsZero())
// Set a time
now := time.Now()
ds.lastRun = now
// Should return the set time
assert.Equal(t, now, ds.LastRun())
}