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