directory_test.go raw

   1  package spider
   2  
   3  import (
   4  	"context"
   5  	"testing"
   6  	"time"
   7  
   8  	"next.orly.dev/pkg/nostr/encoders/event"
   9  	"next.orly.dev/pkg/nostr/encoders/kind"
  10  	"next.orly.dev/pkg/nostr/encoders/tag"
  11  	"github.com/stretchr/testify/assert"
  12  	"github.com/stretchr/testify/require"
  13  )
  14  
  15  func TestExtractRelaysFromEvents(t *testing.T) {
  16  	ds := &DirectorySpider{}
  17  
  18  	tests := []struct {
  19  		name     string
  20  		events   []*event.E
  21  		expected []string
  22  	}{
  23  		{
  24  			name:     "empty events",
  25  			events:   []*event.E{},
  26  			expected: []string{},
  27  		},
  28  		{
  29  			name: "single event with relays",
  30  			events: []*event.E{
  31  				{
  32  					Kind: kind.RelayListMetadata.K,
  33  					Tags: &tag.S{
  34  						tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay1.example.com")),
  35  						tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay2.example.com")),
  36  					},
  37  				},
  38  			},
  39  			expected: []string{"wss://relay1.example.com", "wss://relay2.example.com"},
  40  		},
  41  		{
  42  			name: "multiple events with duplicate relays",
  43  			events: []*event.E{
  44  				{
  45  					Kind: kind.RelayListMetadata.K,
  46  					Tags: &tag.S{
  47  						tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay1.example.com")),
  48  					},
  49  				},
  50  				{
  51  					Kind: kind.RelayListMetadata.K,
  52  					Tags: &tag.S{
  53  						tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay1.example.com")),
  54  						tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay3.example.com")),
  55  					},
  56  				},
  57  			},
  58  			expected: []string{"wss://relay1.example.com", "wss://relay3.example.com"},
  59  		},
  60  		{
  61  			name: "event with empty r tags",
  62  			events: []*event.E{
  63  				{
  64  					Kind: kind.RelayListMetadata.K,
  65  					Tags: &tag.S{
  66  						tag.NewFromBytesSlice([]byte("r")), // empty value
  67  						tag.NewFromBytesSlice([]byte("r"), []byte("wss://valid.relay.com")),
  68  					},
  69  				},
  70  			},
  71  			expected: []string{"wss://valid.relay.com"},
  72  		},
  73  		{
  74  			name: "normalizes relay URLs",
  75  			events: []*event.E{
  76  				{
  77  					Kind: kind.RelayListMetadata.K,
  78  					Tags: &tag.S{
  79  						tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay.example.com")),
  80  						tag.NewFromBytesSlice([]byte("r"), []byte("wss://relay.example.com/")), // duplicate with trailing slash
  81  					},
  82  				},
  83  			},
  84  			expected: []string{"wss://relay.example.com"},
  85  		},
  86  	}
  87  
  88  	for _, tt := range tests {
  89  		t.Run(tt.name, func(t *testing.T) {
  90  			result := ds.extractRelaysFromEvents(tt.events)
  91  
  92  			// For empty case, check length
  93  			if len(tt.expected) == 0 {
  94  				assert.Empty(t, result)
  95  				return
  96  			}
  97  
  98  			// Check that all expected relays are present (order may vary)
  99  			assert.Len(t, result, len(tt.expected))
 100  			for _, expected := range tt.expected {
 101  				assert.Contains(t, result, expected)
 102  			}
 103  		})
 104  	}
 105  }
 106  
 107  func TestDirectorySpiderLifecycle(t *testing.T) {
 108  	ctx, cancel := context.WithCancel(context.Background())
 109  	defer cancel()
 110  
 111  	// Create spider without database (will return error)
 112  	_, err := NewDirectorySpider(ctx, nil, nil, 0, 0)
 113  	require.Error(t, err)
 114  	assert.Contains(t, err.Error(), "database cannot be nil")
 115  }
 116  
 117  func TestDirectorySpiderDefaults(t *testing.T) {
 118  	// Test that defaults are applied correctly
 119  	assert.Equal(t, 24*time.Hour, DirectorySpiderDefaultInterval)
 120  	assert.Equal(t, 3, DirectorySpiderDefaultMaxHops)
 121  	assert.Equal(t, 30*time.Second, DirectorySpiderRelayTimeout)
 122  	assert.Equal(t, 60*time.Second, DirectorySpiderQueryTimeout)
 123  	assert.Equal(t, 500*time.Millisecond, DirectorySpiderRelayDelay)
 124  	assert.Equal(t, 5000, DirectorySpiderMaxEventsPerQuery)
 125  }
 126  
 127  func TestTriggerNow(t *testing.T) {
 128  	ctx, cancel := context.WithCancel(context.Background())
 129  	defer cancel()
 130  
 131  	ds := &DirectorySpider{
 132  		ctx:         ctx,
 133  		triggerChan: make(chan struct{}, 1),
 134  	}
 135  
 136  	// First trigger should succeed
 137  	ds.TriggerNow()
 138  
 139  	// Verify trigger was sent
 140  	select {
 141  	case <-ds.triggerChan:
 142  		// Expected
 143  	default:
 144  		t.Error("trigger was not sent")
 145  	}
 146  
 147  	// Second trigger while channel is empty should also succeed
 148  	ds.TriggerNow()
 149  
 150  	// But if we trigger again without draining, it should not block
 151  	ds.TriggerNow() // Should not block due to select default case
 152  }
 153  
 154  func TestLastRun(t *testing.T) {
 155  	ds := &DirectorySpider{}
 156  
 157  	// Initially should be zero
 158  	assert.True(t, ds.LastRun().IsZero())
 159  
 160  	// Set a time
 161  	now := time.Now()
 162  	ds.lastRun = now
 163  
 164  	// Should return the set time
 165  	assert.Equal(t, now, ds.LastRun())
 166  }
 167