nostrb 0.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/test/event.rb ADDED
@@ -0,0 +1,193 @@
1
+ require 'nostrb/event'
2
+ require_relative 'common.rb'
3
+ require 'minitest/autorun'
4
+
5
+ include Nostrb
6
+
7
+ describe Event do
8
+ def text_note(content = '')
9
+ Event.new(content, kind: 1, pk: Test::PK)
10
+ end
11
+
12
+ describe "class functions" do
13
+ it "computes a 32 byte digest of a JSON serialization" do
14
+ a = SignedEvent.serialize(Test::STATIC_HASH)
15
+ d = Event.digest(a)
16
+ expect(d).must_be_kind_of String
17
+ expect(d.length).must_equal 32
18
+ expect(d.encoding).must_equal Encoding::BINARY
19
+ end
20
+ end
21
+
22
+ describe "initialization" do
23
+ it "wraps a string of content" do
24
+ content = 'hello world'
25
+ expect(text_note(content).content).must_equal content
26
+ end
27
+
28
+ it "requires a _kind_ integer, defaulting to 1" do
29
+ expect(Event.new(kind: 0, pk: Test::PK).kind).must_equal 0
30
+ expect(Event.new(pk: Test::PK).kind).must_equal 1
31
+ end
32
+
33
+ it "requires a public key in binary format" do
34
+ expect(Test::EVENT.pk).must_equal Test::PK
35
+ expect {
36
+ Event.new(kind: 1, pk: SchnorrSig.bin2hex(Test::PK))
37
+ }.must_raise EncodingError
38
+ expect {
39
+ Event.new(kind: 1, pk: "0123456789abcdef".b)
40
+ }.must_raise SizeError
41
+ expect { Event.new }.must_raise
42
+ end
43
+ end
44
+
45
+ it "provides its content in a string context" do
46
+ s = text_note('hello').to_s
47
+ expect(s).must_equal 'hello'
48
+ end
49
+
50
+ it "serializes to an array starting with 0, length 6" do
51
+ a = text_note('hello').to_a
52
+ expect(a).must_be_kind_of Array
53
+ expect(a[0]).must_equal 0
54
+ expect(a.length).must_equal 6
55
+ expect(a[5]).must_equal 'hello'
56
+ end
57
+
58
+ it "has a pubkey in hex format" do
59
+ pubkey = Test::EVENT.pubkey
60
+ expect(pubkey).must_be_kind_of String
61
+ expect(pubkey.length).must_equal 64
62
+ end
63
+
64
+ it "requires a timestamp to create a SHA256 digest" do
65
+ e = Test::EVENT
66
+ d = e.digest(Time.now.to_i)
67
+ expect(d).must_be_kind_of String
68
+ expect(d.length).must_equal 32
69
+ expect(d.encoding).must_equal Encoding::BINARY
70
+ end
71
+
72
+ it "provides a SignedEvent when signed with a secret key" do
73
+ expect(Test::EVENT.sign(Test::SK)).must_be_kind_of SignedEvent
74
+ end
75
+
76
+ describe "event tags" do
77
+ it "supports tags in the form of Array[Array[String]]" do
78
+ e = text_note()
79
+ expect(e.tags).must_be_kind_of Array
80
+ expect(e.tags).must_be_empty
81
+
82
+ e.add_tag('tag', 'value')
83
+ expect(e.tags).wont_be_empty
84
+ expect(e.tags.length).must_equal 1
85
+
86
+ tags0 = e.tags[0]
87
+ expect(tags0.length).must_equal 2
88
+ expect(tags0[0]).must_equal 'tag'
89
+ expect(tags0[1]).must_equal 'value'
90
+
91
+ e.add_tag('foo', 'bar', 'baz')
92
+ expect(e.tags.length).must_equal 2
93
+
94
+ tags1 = e.tags[1]
95
+ expect(tags1.length).must_equal 3
96
+ expect(tags1[2]).must_equal 'baz'
97
+ end
98
+
99
+ it "references prior events" do
100
+ p = text_note()
101
+ s = p.sign(Test::SK)
102
+
103
+ e = text_note()
104
+ e.ref_event(s.id)
105
+ expect(e.tags).wont_be_empty
106
+ expect(e.tags.length).must_equal 1
107
+ expect(e.tags[0][0]).must_equal 'e'
108
+ expect(e.tags[0][1]).must_equal s.id
109
+ end
110
+
111
+ it "references known public keys" do
112
+ e = text_note()
113
+ pubkey = SchnorrSig.bin2hex Test::PK
114
+ e.ref_pubkey(pubkey)
115
+ expect(e.tags).wont_be_empty
116
+ expect(e.tags.length).must_equal 1
117
+ expect(e.tags[0][0]).must_equal 'p'
118
+ expect(e.tags[0][1]).must_equal pubkey
119
+ end
120
+ end
121
+ end
122
+
123
+ describe SignedEvent do
124
+ def signed_note(content = '')
125
+ Event.new(content, kind: 1, pk: Test::PK).sign(Test::SK)
126
+ end
127
+
128
+ describe "class functions" do
129
+ it "validates a JSON parsed hash" do
130
+ h = SignedEvent.validate!(Test::STATIC_HASH)
131
+ expect(h).must_be_kind_of Hash
132
+ %w[id pubkey kind content tags created_at sig].each { |k|
133
+ expect(h.key?(k)).must_equal true
134
+ }
135
+ end
136
+
137
+ it "verifies a JSON parsed hash" do
138
+ h = SignedEvent.verify(Test::STATIC_HASH)
139
+ expect(h).must_be_kind_of Hash
140
+ end
141
+
142
+ it "serializes a JSON parsed hash" do
143
+ a = SignedEvent.serialize(Test::STATIC_HASH)
144
+ expect(a).must_be_kind_of Array
145
+ expect(a.length).must_equal 6
146
+ end
147
+
148
+ it "digests a hash JSON parsed hash, which it will serialize" do
149
+ a = SignedEvent.serialize(Test::STATIC_HASH)
150
+ d = Event.digest(a)
151
+ d2 = SignedEvent.digest(Test::STATIC_HASH)
152
+ expect(d2).must_equal d
153
+ end
154
+ end
155
+
156
+ it "generates a timestamp at creation time" do
157
+ expect(signed_note().created_at).must_be_kind_of Integer
158
+ end
159
+
160
+ it "signs the event, given a private key in binary format" do
161
+ signed = signed_note()
162
+ expect(signed).must_be_kind_of SignedEvent
163
+ expect(signed.id).must_be_kind_of String
164
+ expect(signed.created_at).must_be_kind_of Integer
165
+
166
+ # check signature
167
+ signature = signed.signature
168
+ expect(signature).must_be_kind_of String
169
+ expect(signature.encoding).must_equal Encoding::BINARY
170
+ expect(signature.length).must_equal 64
171
+
172
+ # check sig hex
173
+ sig = signed.sig
174
+ expect(sig).must_be_kind_of String
175
+ expect(sig.encoding).wont_equal Encoding::BINARY
176
+ expect(sig.length).must_equal 128
177
+ end
178
+
179
+ it "has a formalized Key-Value format" do
180
+ h = signed_note().to_h
181
+ expect(h).must_be_kind_of Hash
182
+ expect(h.fetch "content").must_be_kind_of String
183
+ expect(h.fetch "pubkey").must_be_kind_of String
184
+ expect(h["pubkey"]).wont_be_empty
185
+ expect(h.fetch "kind").must_be_kind_of Integer
186
+ expect(h.fetch "tags").must_be_kind_of Array
187
+ expect(h.fetch "created_at").must_be_kind_of Integer
188
+ expect(h.fetch "id").must_be_kind_of String
189
+ expect(h["id"]).wont_be_empty
190
+ expect(h.fetch "sig").must_be_kind_of String
191
+ expect(h["sig"]).wont_be_empty
192
+ end
193
+ end
data/test/nostrb.rb ADDED
@@ -0,0 +1,87 @@
1
+ require 'nostrb'
2
+ require 'minitest/autorun'
3
+
4
+ describe Nostrb do
5
+ describe "module functions" do
6
+ describe "type enforcement" do
7
+ it "can check any class" do
8
+ str = 'asdf'
9
+ expect(Nostrb.check!(str, String)).must_equal str
10
+ expect { Nostrb.check!(str, Range) }.must_raise TypeError
11
+
12
+ range = (0..10)
13
+ expect(Nostrb.check!(range, Range)).must_equal range
14
+ expect { Nostrb.check!(range, Symbol) }.must_raise TypeError
15
+
16
+ sym = :symbol
17
+ expect(Nostrb.check!(sym, Symbol)).must_equal sym
18
+ expect { Nostrb.check!(sym, String) }.must_raise TypeError
19
+ end
20
+
21
+ it "validates a text (possibly hex) string" do
22
+ hex = "0123456789abcdef"
23
+
24
+ expect(Nostrb.txt!(hex)).must_equal hex
25
+ expect(Nostrb.txt!(hex, length: 16)).must_equal hex
26
+ expect { Nostrb.txt!(hex, length: 8) }.must_raise Nostrb::SizeError
27
+ expect { Nostrb.txt!("0123".b) }.must_raise EncodingError
28
+ end
29
+
30
+ it "enforces Integer class where expected" do
31
+ int = 1234
32
+ expect(Nostrb.int!(int)).must_equal int
33
+ expect { Nostrb.int!('1234') }.must_raise TypeError
34
+ end
35
+
36
+ it "enforces a particular tag structure where expected" do
37
+ # Array[Array[String]]
38
+ tags = [['a', 'b', 'c'], ['1', '2', '3', '4']]
39
+ expect(Nostrb.tags!(tags)).must_equal tags
40
+
41
+ [
42
+ ['a', 'b', 'c', '1' , '2', '3', '4'], # Array[String]
43
+ [['a', 'b', 'c'], [1, 2, 3, 4]], # Array[Array[String|Integer]]
44
+ ['a', 'b', 'c', ['1' , '2', '3', '4']],# Array[Array | String]
45
+ 'a', # String
46
+ [[:a, :b, :c], [1, 2, 3, 4]], # Array[Array[Symbol|String]]
47
+ ].each { |bad|
48
+ expect { Nostrb.tags!(bad) }.must_raise TypeError
49
+ }
50
+ end
51
+ end
52
+
53
+ describe "JSON I/O" do
54
+ it "parses a JSON string to a Ruby object" do
55
+ expect(Nostrb.parse('{}')).must_equal Hash.new
56
+ expect(Nostrb.parse('[]')).must_equal Array.new
57
+ end
58
+
59
+ it "generates JSON from a Ruby hash or array" do
60
+ expect(Nostrb.json({})).must_equal '{}'
61
+ expect(Nostrb.json([])).must_equal '[]'
62
+ end
63
+ end
64
+
65
+ describe "SHA256 digest" do
66
+ it "generates 32 bytes binary, given any string" do
67
+ strings = ["\x01\x02".b, '1234', 'asdf', '']
68
+ digests = strings.map { |s| Nostrb.digest(s) }
69
+
70
+ digests.each { |d|
71
+ expect(d).must_be_kind_of String
72
+ expect(d.encoding).must_equal Encoding::BINARY
73
+ expect(d.length).must_equal 32
74
+ }
75
+ end
76
+
77
+ it "generates the same output for the same input" do
78
+ strings = ["\x01\x02".b, '1234', 'asdf', '']
79
+ digests = strings.map { |s| Nostrb.digest(s) }
80
+
81
+ strings.each.with_index { |s, i|
82
+ expect(Nostrb.digest(s)).must_equal digests[i]
83
+ }
84
+ end
85
+ end
86
+ end
87
+ end
data/test/relay.rb ADDED
@@ -0,0 +1,360 @@
1
+ require 'nostrb/relay'
2
+ require 'nostrb/source'
3
+ require_relative 'common'
4
+ require 'minitest/autorun'
5
+
6
+ include Nostrb
7
+
8
+
9
+ # this can be set by GitHubActions
10
+ DB_FILE = ENV['INPUT_DB_FILE'] || 'testing.db'
11
+
12
+ # use SQLite backing
13
+ SQLite::Setup.new(DB_FILE).setup
14
+
15
+ describe Server do
16
+ def valid_response!(resp)
17
+ types = ["EVENT", "OK", "EOSE", "CLOSED", "NOTICE"]
18
+ expect(resp).must_be_kind_of Array
19
+ expect(resp.length).must_be :>=, 2
20
+ expect(resp.length).must_be :<=, 4
21
+ resp[0..1].each { |s| expect(s).must_be_kind_of String }
22
+ expect(resp[0].upcase).must_equal resp[0]
23
+ expect(types.include?(resp[0])).must_equal true
24
+ end
25
+
26
+ describe "class functions" do
27
+ it "has an EVENT response, given subscriber_id and requested event" do
28
+ sid = '1234'
29
+ resp = Server.event(sid, Test::SIGNED)
30
+ valid_response!(resp)
31
+ expect(resp[0]).must_equal "EVENT"
32
+ expect(resp[1]).must_equal sid
33
+ expect(resp[2]).must_be_kind_of Hash
34
+ expect(resp[2]).wont_be_empty
35
+ end
36
+
37
+ it "has an OK response, given an event_id" do
38
+ # positive ok
39
+ resp = Server.ok(Test::SIGNED.id)
40
+ valid_response!(resp)
41
+ expect(resp[0]).must_equal "OK"
42
+ expect(resp[1]).must_equal Test::SIGNED.id
43
+ expect(resp[2]).must_equal true
44
+ expect(resp[3]).must_be_kind_of String # empty by default
45
+
46
+ # negative ok
47
+ resp = Server.ok(Test::SIGNED.id, "error: testing", ok: false)
48
+ valid_response!(resp)
49
+ expect(resp[0]).must_equal "OK"
50
+ expect(resp[1]).must_equal Test::SIGNED.id
51
+ expect(resp[2]).must_equal false
52
+ expect(resp[3]).must_be_kind_of String
53
+ expect(resp[3]).wont_be_empty
54
+
55
+ # ok:false requires nonempty message
56
+ expect {
57
+ Server.ok(Test::SIGNED.id, "", ok: false)
58
+ }.must_raise FormatError
59
+ expect { Server.ok(Test::SIGNED.id, ok: false) }.must_raise FormatError
60
+ end
61
+
62
+ it "has an EOSE response to conclude a series of EVENT responses" do
63
+ sid = '1234'
64
+ resp = Server.eose(sid)
65
+ valid_response!(resp)
66
+ expect(resp[0]).must_equal "EOSE"
67
+ expect(resp[1]).must_equal sid
68
+ end
69
+
70
+ it "has a CLOSED response to shut down a subscriber" do
71
+ sid = '1234'
72
+ msg = "closed: bye"
73
+ resp = Server.closed(sid, msg)
74
+ valid_response!(resp)
75
+ expect(resp[0]).must_equal "CLOSED"
76
+ expect(resp[1]).must_equal sid
77
+ expect(resp[2]).must_equal msg
78
+ end
79
+
80
+ it "has a NOTICE response to provide any message to the user" do
81
+ msg = "all i ever really wanna do is get nice, " +
82
+ "get loose and goof this little slice of life"
83
+ resp = Server.notice(msg)
84
+ valid_response!(resp)
85
+ expect(resp[0]).must_equal "NOTICE"
86
+ expect(resp[1]).must_equal msg
87
+ end
88
+
89
+ it "formats Exceptions to a common string representation" do
90
+ r = RuntimeError.new("stuff")
91
+ expect(r).must_be_kind_of Exception
92
+ expect(Server.message(r)).must_equal "RuntimeError: stuff"
93
+
94
+ e = Nostrb::Error.new("things")
95
+ expect(e).must_be_kind_of Exception
96
+ expect(Server.message(e)).must_equal "Error: things"
97
+ end
98
+
99
+ it "uses NOTICE to return errors" do
100
+ e = RuntimeError.new "stuff"
101
+ resp = Server.error(e)
102
+ valid_response!(resp)
103
+ expect(resp[0]).must_equal "NOTICE"
104
+ expect(resp[1]).must_equal "RuntimeError: stuff"
105
+ end
106
+ end
107
+
108
+ it "has no initialization parameters" do
109
+ s = Server.new(DB_FILE)
110
+ expect(s).must_be_kind_of Server
111
+ end
112
+
113
+ # respond OK: true
114
+ it "has a single response to EVENT requests" do
115
+ json = Nostrb.json(Source.publish(Test::SIGNED))
116
+ responses = Server.new(DB_FILE).ingest(json)
117
+ expect(responses).must_be_kind_of Array
118
+ expect(responses.length).must_equal 1
119
+
120
+ resp = responses[0]
121
+ expect(resp).must_be_kind_of Array
122
+ expect(resp[0]).must_equal "OK"
123
+ expect(resp[1]).must_equal Test::SIGNED.id
124
+ expect(resp[2]).must_equal true
125
+ end
126
+
127
+ # store and retrieve with a subscription filter
128
+ it "stores inbound events" do
129
+ s = Server.new(DB_FILE)
130
+ sk, pk = SchnorrSig.keypair
131
+ e = Event.new('sqlite', pk: pk).sign(sk)
132
+ resp = s.ingest Nostrb.json(Source.publish(e))
133
+ expect(resp).must_be_kind_of Array
134
+ expect(resp[0]).must_be_kind_of Array
135
+ expect(resp[0][0]).must_equal "OK"
136
+
137
+ pubkey = SchnorrSig.bin2hex(pk)
138
+
139
+ f = Filter.new
140
+ f.add_authors pubkey
141
+ f.add_ids e.id
142
+
143
+ resp = s.ingest Nostrb.json(Source.subscribe(pubkey, f))
144
+ expect(resp).must_be_kind_of Array
145
+ expect(resp.length).must_equal 2
146
+ event, eose = *resp
147
+ expect(event[0]).must_equal 'EVENT'
148
+ expect(event[1]).must_equal pubkey
149
+ expect(event[2]).must_be_kind_of Hash
150
+ expect(event[2]['id']).must_equal e.id
151
+ expect(eose[0]).must_equal 'EOSE'
152
+ end
153
+
154
+ it "has multiple responses to REQ requets" do
155
+ s = Server.new(DB_FILE)
156
+ sk, pk = SchnorrSig.keypair
157
+ e = Event.new('first', pk: pk).sign(sk)
158
+ resp = s.ingest Nostrb.json(Source.publish(e))
159
+ expect(resp).must_be_kind_of Array
160
+ expect(resp[0]).must_be_kind_of Array
161
+ expect(resp[0][0]).must_equal "OK"
162
+
163
+ e2 = Event.new('second', pk: pk).sign(sk)
164
+ resp = s.ingest Nostrb.json(Source.publish(e2))
165
+ expect(resp).must_be_kind_of Array
166
+ expect(resp[0]).must_be_kind_of Array
167
+ expect(resp[0][0]).must_equal "OK"
168
+
169
+ # with no filters, nothing will match
170
+ sid = e.pubkey
171
+ responses = s.ingest(Nostrb.json(Source.subscribe(sid)))
172
+ expect(responses).must_be_kind_of Array
173
+ expect(responses.length).must_equal 1
174
+ resp = responses[0]
175
+ expect(resp).must_be_kind_of Array
176
+ expect(resp[0]).must_equal "EOSE"
177
+ expect(resp[1]).must_equal sid
178
+
179
+ # now add a filter based on pubkey
180
+ f = Filter.new
181
+ f.add_authors e.pubkey
182
+ f.add_ids e.id, e2.id
183
+
184
+ resp = s.ingest Nostrb.json(Source.subscribe(sid, f))
185
+ expect(resp).must_be_kind_of Array
186
+ expect(resp.length).must_equal 3
187
+
188
+ # remove EOSE and validate
189
+ eose = resp.pop
190
+ expect(eose).must_be_kind_of Array
191
+ expect(eose[0]).must_equal "EOSE"
192
+ expect(eose[1]).must_equal sid
193
+
194
+ # verify the response event ids
195
+ resp.each { |event|
196
+ expect(event).must_be_kind_of Array
197
+ expect(event[0]).must_equal "EVENT"
198
+ expect(event[1]).must_equal sid
199
+ hsh = event[2]
200
+ expect(hsh).must_be_kind_of Hash
201
+ expect(SignedEvent.validate!(hsh)).must_equal hsh
202
+ expect([e.id, e2.id]).must_include hsh["id"]
203
+ }
204
+ end
205
+
206
+ it "has a single response to CLOSE requests" do
207
+ s = Server.new(DB_FILE)
208
+ sid = Test::EVENT.pubkey
209
+ responses = s.ingest(Nostrb.json(Source.close(sid)))
210
+
211
+ # respond CLOSED
212
+ expect(responses).must_be_kind_of Array
213
+ expect(responses.length).must_equal 1
214
+
215
+ resp = responses[0]
216
+ expect(resp).must_be_kind_of Array
217
+ expect(resp[0]).must_equal "CLOSED"
218
+ expect(resp[1]).must_equal sid
219
+ end
220
+
221
+ describe "error handling" do
222
+ # invalid request type
223
+ it "handles unknown unknown request types with an error notice" do
224
+ a = Source.publish Test::SIGNED
225
+ a[0] = 'NONSENSE'
226
+ responses = Server.new(DB_FILE).ingest(Nostrb.json(a))
227
+ expect(responses).must_be_kind_of Array
228
+ expect(responses.length).must_equal 1
229
+
230
+ resp = responses[0]
231
+ expect(resp).must_be_kind_of Array
232
+ expect(resp[0]).must_equal "NOTICE"
233
+ expect(resp[1]).must_be_kind_of String
234
+ expect(resp[1]).wont_be_empty
235
+ end
236
+
237
+ # replace leading open brace with space
238
+ it "handles JSON parse errors with an error notice" do
239
+ j = Nostrb.json(Nostrb::Source.publish(Test::SIGNED))
240
+ expect(j[9]).must_equal '{'
241
+ j[9] = ' '
242
+ resp = Server.new(DB_FILE).ingest(j)
243
+ expect(resp).must_be_kind_of Array
244
+ expect(resp.length).must_equal 1
245
+
246
+ type, msg = *resp.first
247
+ expect(type).must_equal "NOTICE"
248
+ expect(msg).must_be_kind_of String
249
+ expect(msg).wont_be_empty
250
+ end
251
+
252
+ # add "stuff":"things"
253
+ it "handles unexpected fields with an error notice" do
254
+ a = Nostrb::Source.publish(Test::SIGNED)
255
+ expect(a[1]).must_be_kind_of Hash
256
+ a[1]["stuff"] = "things"
257
+
258
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
259
+ expect(resp).must_be_kind_of Array
260
+ expect(resp.length).must_equal 1
261
+
262
+ type, msg = *resp.first
263
+ expect(type).must_equal "NOTICE"
264
+ expect(msg).must_be_kind_of String
265
+ expect(msg).wont_be_empty
266
+ end
267
+
268
+ # remove "tags"
269
+ it "handles missing fields with an error notice" do
270
+ a = Nostrb::Source.publish(Test::SIGNED)
271
+ expect(a[1]).must_be_kind_of Hash
272
+ a[1].delete("tags")
273
+
274
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
275
+ expect(resp).must_be_kind_of Array
276
+ expect(resp.length).must_equal 1
277
+
278
+ type, msg = *resp.first
279
+ expect(type).must_equal "NOTICE"
280
+ expect(msg).must_be_kind_of String
281
+ expect(msg).wont_be_empty
282
+ end
283
+
284
+ # cut "id" in half
285
+ it "handles field format errors with an error notice" do
286
+ a = Nostrb::Source.publish(Test::SIGNED)
287
+ expect(a[1]).must_be_kind_of Hash
288
+ a[1]["id"] = a[1]["id"].slice(0, 32)
289
+
290
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
291
+ expect(resp).must_be_kind_of Array
292
+ expect(resp.length).must_equal 1
293
+
294
+ type, msg = *resp.first
295
+ expect(type).must_equal "NOTICE"
296
+ expect(msg).must_be_kind_of String
297
+ expect(msg).wont_be_empty
298
+ end
299
+
300
+ # random "sig"
301
+ it "handles invalid signature with OK:false" do
302
+ a = Nostrb::Source.publish(Test::SIGNED)
303
+ expect(a[1]).must_be_kind_of Hash
304
+ a[1]["sig"] = SchnorrSig.bin2hex(Random.bytes(64))
305
+
306
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
307
+ expect(resp).must_be_kind_of Array
308
+ expect(resp.length).must_equal 1
309
+
310
+ type, id, value, msg = *resp.first
311
+ expect(type).must_equal "OK"
312
+ expect(id).must_equal a[1]["id"]
313
+ expect(value).must_equal false
314
+ expect(msg).must_be_kind_of String
315
+ expect(msg).wont_be_empty
316
+ expect(msg).must_match(/SignatureCheck/)
317
+ end
318
+
319
+ # "id" and "sig" spoofed from another event
320
+ it "handles spoofed id with OK:false" do
321
+ orig = Source.publish(Test.new_event('orig'))
322
+ spoof = Source.publish(Test::SIGNED)
323
+
324
+ orig[1]["id"] = spoof[1]["id"]
325
+ orig[1]["sig"] = spoof[1]["sig"]
326
+
327
+ # now sig and id agree with each other, but not orig's content/metadata
328
+ # the signature should verify, but the id should not
329
+
330
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(orig))
331
+ expect(resp).must_be_kind_of Array
332
+ expect(resp.length).must_equal 1
333
+
334
+ type, id, value, msg = *resp.first
335
+ expect(type).must_equal "OK"
336
+ expect(id).must_equal orig[1]["id"]
337
+ expect(value).must_equal false
338
+ expect(msg).must_be_kind_of String
339
+ expect(msg).wont_be_empty
340
+ expect(msg).must_match(/IdCheck/)
341
+ end
342
+
343
+ # random "id"
344
+ it "handles invalid id with OK:false" do
345
+ a = Source.publish(Test::SIGNED)
346
+ a[1]["id"] = SchnorrSig.bin2hex(Random.bytes(32))
347
+
348
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
349
+ expect(resp).must_be_kind_of Array
350
+ expect(resp.length).must_equal 1
351
+
352
+ type, id, value, msg = *resp.first
353
+ expect(type).must_equal "OK"
354
+ expect(id).must_equal a[1]["id"]
355
+ expect(value).must_equal false
356
+ expect(msg).must_be_kind_of String
357
+ expect(msg).wont_be_empty
358
+ end
359
+ end
360
+ end