validation_test.go raw

   1  package validation
   2  
   3  import (
   4  	"testing"
   5  	"time"
   6  
   7  	"next.orly.dev/pkg/nostr/encoders/event"
   8  	"next.orly.dev/pkg/nostr/encoders/tag"
   9  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  10  )
  11  
  12  func TestNew(t *testing.T) {
  13  	s := New()
  14  	if s == nil {
  15  		t.Fatal("New() returned nil")
  16  	}
  17  	if s.cfg == nil {
  18  		t.Fatal("New() returned service with nil config")
  19  	}
  20  	if s.cfg.MaxFutureSeconds != 3600 {
  21  		t.Errorf("expected MaxFutureSeconds=3600, got %d", s.cfg.MaxFutureSeconds)
  22  	}
  23  }
  24  
  25  func TestNewWithConfig(t *testing.T) {
  26  	cfg := &Config{MaxFutureSeconds: 7200}
  27  	s := NewWithConfig(cfg)
  28  	if s.cfg.MaxFutureSeconds != 7200 {
  29  		t.Errorf("expected MaxFutureSeconds=7200, got %d", s.cfg.MaxFutureSeconds)
  30  	}
  31  
  32  	// Test nil config defaults
  33  	s = NewWithConfig(nil)
  34  	if s.cfg.MaxFutureSeconds != 3600 {
  35  		t.Errorf("expected default MaxFutureSeconds=3600, got %d", s.cfg.MaxFutureSeconds)
  36  	}
  37  }
  38  
  39  func TestResultConstructors(t *testing.T) {
  40  	// Test OK
  41  	r := OK()
  42  	if !r.Valid || r.Code != ReasonNone || r.Msg != "" {
  43  		t.Error("OK() should return Valid=true with no code/msg")
  44  	}
  45  
  46  	// Test Blocked
  47  	r = Blocked("test blocked")
  48  	if r.Valid || r.Code != ReasonBlocked || r.Msg != "test blocked" {
  49  		t.Error("Blocked() should return Valid=false with ReasonBlocked")
  50  	}
  51  
  52  	// Test Invalid
  53  	r = Invalid("test invalid")
  54  	if r.Valid || r.Code != ReasonInvalid || r.Msg != "test invalid" {
  55  		t.Error("Invalid() should return Valid=false with ReasonInvalid")
  56  	}
  57  
  58  	// Test Error
  59  	r = Error("test error")
  60  	if r.Valid || r.Code != ReasonError || r.Msg != "test error" {
  61  		t.Error("Error() should return Valid=false with ReasonError")
  62  	}
  63  }
  64  
  65  func TestValidateRawJSON_LowercaseHex(t *testing.T) {
  66  	s := New()
  67  
  68  	// Valid lowercase hex
  69  	validJSON := []byte(`["EVENT",{"id":"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789","pubkey":"fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210","created_at":1234567890,"kind":1,"tags":[],"content":"test","sig":"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"}]`)
  70  
  71  	result := s.ValidateRawJSON(validJSON)
  72  	if !result.Valid {
  73  		t.Errorf("valid lowercase JSON should pass: %s", result.Msg)
  74  	}
  75  
  76  	// Invalid - uppercase in id
  77  	invalidID := []byte(`["EVENT",{"id":"ABCDEF0123456789abcdef0123456789abcdef0123456789abcdef0123456789","pubkey":"fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210","created_at":1234567890,"kind":1,"tags":[],"content":"test","sig":"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"}]`)
  78  
  79  	result = s.ValidateRawJSON(invalidID)
  80  	if result.Valid {
  81  		t.Error("uppercase in id should fail validation")
  82  	}
  83  	if result.Code != ReasonBlocked {
  84  		t.Error("uppercase hex should return ReasonBlocked")
  85  	}
  86  }
  87  
  88  func TestValidateEvent_ValidEvent(t *testing.T) {
  89  	s := New()
  90  
  91  	// Create and sign a valid event
  92  	sign := p8k.MustNew()
  93  	if err := sign.Generate(); err != nil {
  94  		t.Fatalf("failed to generate signer: %v", err)
  95  	}
  96  
  97  	ev := event.New()
  98  	ev.Kind = 1
  99  	ev.CreatedAt = time.Now().Unix()
 100  	ev.Content = []byte("test content")
 101  	ev.Tags = tag.NewS()
 102  
 103  	if err := ev.Sign(sign); err != nil {
 104  		t.Fatalf("failed to sign event: %v", err)
 105  	}
 106  
 107  	result := s.ValidateEvent(ev)
 108  	if !result.Valid {
 109  		t.Errorf("valid event should pass validation: %s", result.Msg)
 110  	}
 111  }
 112  
 113  func TestValidateEvent_InvalidID(t *testing.T) {
 114  	s := New()
 115  
 116  	// Create a valid event then corrupt the ID
 117  	sign := p8k.MustNew()
 118  	if err := sign.Generate(); err != nil {
 119  		t.Fatalf("failed to generate signer: %v", err)
 120  	}
 121  
 122  	ev := event.New()
 123  	ev.Kind = 1
 124  	ev.CreatedAt = time.Now().Unix()
 125  	ev.Content = []byte("test content")
 126  	ev.Tags = tag.NewS()
 127  
 128  	if err := ev.Sign(sign); err != nil {
 129  		t.Fatalf("failed to sign event: %v", err)
 130  	}
 131  
 132  	// Corrupt the ID
 133  	ev.ID[0] ^= 0xFF
 134  
 135  	result := s.ValidateEvent(ev)
 136  	if result.Valid {
 137  		t.Error("event with corrupted ID should fail validation")
 138  	}
 139  	if result.Code != ReasonInvalid {
 140  		t.Errorf("invalid ID should return ReasonInvalid, got %d", result.Code)
 141  	}
 142  }
 143  
 144  func TestValidateEvent_FutureTimestamp(t *testing.T) {
 145  	// Use short max future time for testing
 146  	s := NewWithConfig(&Config{MaxFutureSeconds: 10})
 147  
 148  	sign := p8k.MustNew()
 149  	if err := sign.Generate(); err != nil {
 150  		t.Fatalf("failed to generate signer: %v", err)
 151  	}
 152  
 153  	ev := event.New()
 154  	ev.Kind = 1
 155  	ev.CreatedAt = time.Now().Unix() + 3600 // 1 hour in future
 156  	ev.Content = []byte("test content")
 157  	ev.Tags = tag.NewS()
 158  
 159  	if err := ev.Sign(sign); err != nil {
 160  		t.Fatalf("failed to sign event: %v", err)
 161  	}
 162  
 163  	result := s.ValidateEvent(ev)
 164  	if result.Valid {
 165  		t.Error("event with future timestamp should fail validation")
 166  	}
 167  	if result.Code != ReasonInvalid {
 168  		t.Errorf("future timestamp should return ReasonInvalid, got %d", result.Code)
 169  	}
 170  }
 171  
 172  func TestValidateProtectedTag_NoTag(t *testing.T) {
 173  	s := New()
 174  
 175  	ev := event.New()
 176  	ev.Kind = 1
 177  	ev.Tags = tag.NewS()
 178  
 179  	result := s.ValidateProtectedTag(ev, []byte("somepubkey"))
 180  	if !result.Valid {
 181  		t.Error("event without protected tag should pass validation")
 182  	}
 183  }
 184  
 185  func TestValidateProtectedTag_MatchingPubkey(t *testing.T) {
 186  	s := New()
 187  
 188  	ev := event.New()
 189  	ev.Kind = 1
 190  	ev.Pubkey = make([]byte, 32)
 191  	for i := range ev.Pubkey {
 192  		ev.Pubkey[i] = byte(i)
 193  	}
 194  	ev.Tags = tag.NewS()
 195  	*ev.Tags = append(*ev.Tags, tag.NewFromAny("-"))
 196  
 197  	result := s.ValidateProtectedTag(ev, ev.Pubkey)
 198  	if !result.Valid {
 199  		t.Errorf("protected tag with matching pubkey should pass: %s", result.Msg)
 200  	}
 201  }
 202  
 203  func TestValidateProtectedTag_MismatchedPubkey(t *testing.T) {
 204  	s := New()
 205  
 206  	ev := event.New()
 207  	ev.Kind = 1
 208  	ev.Pubkey = make([]byte, 32)
 209  	for i := range ev.Pubkey {
 210  		ev.Pubkey[i] = byte(i)
 211  	}
 212  	ev.Tags = tag.NewS()
 213  	*ev.Tags = append(*ev.Tags, tag.NewFromAny("-"))
 214  
 215  	// Different pubkey for auth
 216  	differentPubkey := make([]byte, 32)
 217  	for i := range differentPubkey {
 218  		differentPubkey[i] = byte(i + 100)
 219  	}
 220  
 221  	result := s.ValidateProtectedTag(ev, differentPubkey)
 222  	if result.Valid {
 223  		t.Error("protected tag with different pubkey should fail validation")
 224  	}
 225  	if result.Code != ReasonBlocked {
 226  		t.Errorf("mismatched protected tag should return ReasonBlocked, got %d", result.Code)
 227  	}
 228  }
 229