Skip to content

Commit d1d4cb9

Browse files
authored
fix: strange behavior with nip 33 parameterized replacable events and nip 40 expiration tag (#316)
* fix: fix content-type on GetInvoiceStatusController * test: fix flaky tests * test: remove cache client from intg tests * chore: lint fix * test: add intg tests for nip-33 events w/ expiration tag
1 parent 33c2fd5 commit d1d4cb9

File tree

8 files changed

+113
-33
lines changed

8 files changed

+113
-33
lines changed

src/controllers/invoices/get-invoice-status-controller.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class GetInvoiceStatusController implements IController {
1919
debug('invalid invoice id: %s', invoiceId)
2020
response
2121
.status(400)
22-
.setHeader('content-type', 'text/plain; charset=utf8')
22+
.setHeader('content-type', 'application/json; charset=utf8')
2323
.send({ id: invoiceId, status: 'invalid invoice' })
2424
return
2525
}
@@ -32,24 +32,24 @@ export class GetInvoiceStatusController implements IController {
3232
debug('invoice not found: %s', invoiceId)
3333
response
3434
.status(404)
35-
.setHeader('content-type', 'text/plain; charset=utf8')
35+
.setHeader('content-type', 'application/json; charset=utf8')
3636
.send({ id: invoiceId, status: 'not found' })
3737
return
3838
}
3939

4040
response
4141
.status(200)
4242
.setHeader('content-type', 'application/json; charset=utf8')
43-
.send(JSON.stringify({
43+
.send({
4444
id: invoice.id,
4545
status: invoice.status,
46-
}))
46+
})
4747
} catch (error) {
4848
console.error(`get-invoice-status-controller: unable to get invoice ${invoiceId}:`, error)
4949

5050
response
5151
.status(500)
52-
.setHeader('content-type', 'text/plain; charset=utf8')
52+
.setHeader('content-type', 'application/json; charset=utf8')
5353
.send({ id: invoiceId, status: 'error' })
5454
}
5555
}

src/handlers/event-message-handler.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -290,14 +290,15 @@ export class EventMessageHandler implements IMessageHandler {
290290

291291
protected addExpirationMetadata(event: Event): Event | ExpiringEvent {
292292
const eventExpiration: number = getEventExpiration(event)
293-
if (eventExpiration) {
294-
const expiringEvent: ExpiringEvent = {
295-
...event,
296-
[EventExpirationTimeMetadataKey]: eventExpiration,
297-
}
298-
return expiringEvent
299-
} else {
293+
if (!eventExpiration) {
300294
return event
301295
}
296+
297+
const expiringEvent: ExpiringEvent = {
298+
...event,
299+
[EventExpirationTimeMetadataKey]: eventExpiration,
300+
}
301+
302+
return expiringEvent
302303
}
303304
}

src/repositories/event-repository.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,9 @@ export class EventRepository implements IEventRepository {
184184
remote_address: path([ContextMetadataKey as any, 'remoteAddress', 'address']),
185185
expires_at: ifElse(
186186
propSatisfies(is(Number), EventExpirationTimeMetadataKey),
187-
prop(EventExpirationTimeMetadataKey as any),
187+
prop(EventExpirationTimeMetadataKey as any),
188188
always(null),
189189
),
190-
191190
})(event)
192191

193192
return this.masterDbClient('events')

src/utils/event.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -285,24 +285,28 @@ export const isDeleteEvent = (event: Event): boolean => {
285285
}
286286

287287
export const isExpiredEvent = (event: Event): boolean => {
288-
if (!event.tags.length) return false
288+
if (!event.tags.length) {
289+
return false
290+
}
289291

290292
const expirationTime = getEventExpiration(event)
291293

292-
if (!expirationTime) return false
294+
if (!expirationTime) {
295+
return false
296+
}
293297

294-
const date = new Date()
295-
const isExpired = expirationTime <= Math.floor(date.getTime() / 1000)
298+
const now = Math.floor(new Date().getTime() / 1000)
296299

297-
return isExpired
300+
return expirationTime <= now
298301
}
299302

300303
export const getEventExpiration = (event: Event): number | undefined => {
301304
const [, rawExpirationTime] = event.tags.find((tag) => tag.length >= 2 && tag[0] === EventTags.Expiration) ?? []
302305
if (!rawExpirationTime) return
303306

304307
const expirationTime = Number(rawExpirationTime)
305-
if ((Number.isSafeInteger(expirationTime) && Math.log10(expirationTime))) {
308+
309+
if ((Number.isSafeInteger(expirationTime) && Math.log10(expirationTime) < 10)) {
306310
return expirationTime
307311
}
308312
}

test/integration/features/nip-16/nip-16.feature

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
Feature: NIP-16 Event treatment
22
Scenario: Alice sends a replaceable event
33
Given someone called Alice
4-
And Alice subscribes to author Alice
54
When Alice sends a replaceable_event_0 event with content "created"
6-
Then Alice receives a replaceable_event_0 event from Alice with content "created"
7-
When Alice sends a replaceable_event_0 event with content "updated"
5+
And Alice sends a replaceable_event_0 event with content "updated"
6+
And Alice subscribes to author Alice
87
Then Alice receives a replaceable_event_0 event from Alice with content "updated"
98
Then Alice unsubscribes from author Alice
109
When Alice subscribes to author Alice
Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
Feature: NIP-33 Parameterized replaceable events
22
Scenario: Alice sends a parameterized replaceable event
33
Given someone called Alice
4-
And Alice subscribes to author Alice
54
When Alice sends a parameterized_replaceable_event_0 event with content "1" and tag d containing "variable"
6-
Then Alice receives a parameterized_replaceable_event_0 event from Alice with content "1" and tag d containing "variable"
75
When Alice sends a parameterized_replaceable_event_0 event with content "2" and tag d containing "variable"
8-
Then Alice receives a parameterized_replaceable_event_0 event from Alice with content "2" and tag d containing "variable"
9-
Then Alice unsubscribes from author Alice
106
When Alice subscribes to author Alice
11-
Then Alice receives 1 parameterized_replaceable_event_0 event from Alice with content "2" and EOSE
7+
Then Alice receives a parameterized_replaceable_event_0 event from Alice with content "2" and tag d containing "variable"
8+
9+
Scenario: Alice adds an expiration tag to a parameterized replaceable event
10+
Given someone called Alice
11+
And someone called Bob
12+
When Alice sends a parameterized_replaceable_event_1 event with content "woot" and tag d containing "stuff"
13+
And Alice sends a parameterized_replaceable_event_1 event with content "nostr.watch" and tag d containing "stuff" and expiring in the future
14+
And Bob subscribes to author Alice
15+
Then Bob receives a parameterized_replaceable_event_1 event from Alice with content "nostr.watch" and tag d containing "stuff"
16+
17+
Scenario: Alice removes an expiration tag to a parameterized replaceable event
18+
Given someone called Alice
19+
And someone called Bob
20+
When Alice sends a parameterized_replaceable_event_1 event with content "nostr.watch" and tag d containing "hey" and expiring in the future
21+
And Alice sends a parameterized_replaceable_event_1 event with content "woot" and tag d containing "hey"
22+
And Bob subscribes to author Alice
23+
Then Bob receives a parameterized_replaceable_event_1 event from Alice with content "woot" and tag d containing "hey"
24+
25+
Scenario: Alice adds and removes an expiration tag to a parameterized replaceable event
26+
Given someone called Alice
27+
And someone called Bob
28+
When Alice sends a parameterized_replaceable_event_1 event with content "first" and tag d containing "friends"
29+
And Alice sends a parameterized_replaceable_event_1 event with content "second" and tag d containing "friends" and expiring in the future
30+
And Alice sends a parameterized_replaceable_event_1 event with content "third" and tag d containing "friends"
31+
And Bob subscribes to author Alice
32+
Then Bob receives a parameterized_replaceable_event_1 event from Alice with content "third" and tag d containing "friends"

test/integration/features/nip-33/nip-33.feature.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { expect } from 'chai'
33
import WebSocket from 'ws'
44

55
import { createEvent, sendEvent, waitForEventCount, waitForNextEvent } from '../helpers'
6+
import { EventKinds, EventTags } from '../../../../src/constants/base'
67
import { Event } from '../../../../src/@types/event'
78

89
When(/^(\w+) sends a parameterized_replaceable_event_0 event with content "([^"]+)" and tag (\w) containing "([^"]+)"$/, async function(
@@ -20,6 +21,52 @@ When(/^(\w+) sends a parameterized_replaceable_event_0 event with content "([^"]
2021
this.parameters.events[name].push(event)
2122
})
2223

24+
When(/^(\w+) sends a parameterized_replaceable_event_1 event with content "([^"]+)" and tag (\w) containing "([^"]+)"$/, async function(
25+
name: string,
26+
content: string,
27+
tag: string,
28+
value: string,
29+
) {
30+
const ws = this.parameters.clients[name] as WebSocket
31+
const { pubkey, privkey } = this.parameters.identities[name]
32+
33+
const event: Event = await createEvent(
34+
{
35+
pubkey,
36+
kind: EventKinds.PARAMETERIZED_REPLACEABLE_FIRST + 1,
37+
content,
38+
tags: [[tag, value]],
39+
},
40+
privkey,
41+
)
42+
43+
await sendEvent(ws, event)
44+
this.parameters.events[name].push(event)
45+
})
46+
47+
When(/^(\w+) sends a parameterized_replaceable_event_1 event with content "([^"]+)" and tag (\w) containing "([^"]+)" and expiring in the future$/, async function(
48+
name: string,
49+
content: string,
50+
tag: string,
51+
value: string,
52+
) {
53+
const ws = this.parameters.clients[name] as WebSocket
54+
const { pubkey, privkey } = this.parameters.identities[name]
55+
56+
const event: Event = await createEvent(
57+
{
58+
pubkey,
59+
kind: EventKinds.PARAMETERIZED_REPLACEABLE_FIRST + 1,
60+
content,
61+
tags: [[tag, value], [EventTags.Expiration, Math.floor(new Date().getTime() / 1000 + 10).toString()]],
62+
},
63+
privkey,
64+
)
65+
66+
await sendEvent(ws, event)
67+
this.parameters.events[name].push(event)
68+
})
69+
2370
Then(
2471
/(\w+) receives a parameterized_replaceable_event_0 event from (\w+) with content "([^"]+?)" and tag (\w+) containing "([^"]+?)"/,
2572
async function(name: string, author: string, content: string, tagName: string, tagValue: string) {
@@ -33,6 +80,19 @@ Then(
3380
expect(receivedEvent.tags[0]).to.deep.equal([tagName, tagValue])
3481
})
3582

83+
Then(
84+
/(\w+) receives a parameterized_replaceable_event_1 event from (\w+) with content "([^"]+?)" and tag (\w+) containing "([^"]+?)"/,
85+
async function(name: string, author: string, content: string, tagName: string, tagValue: string) {
86+
const ws = this.parameters.clients[name] as WebSocket
87+
const subscription = this.parameters.subscriptions[name][this.parameters.subscriptions[name].length - 1]
88+
const receivedEvent = await waitForNextEvent(ws, subscription.name)
89+
90+
expect(receivedEvent.kind).to.equal(30001)
91+
expect(receivedEvent.pubkey).to.equal(this.parameters.identities[author].pubkey)
92+
expect(receivedEvent.content).to.equal(content)
93+
expect(receivedEvent.tags[0]).to.deep.equal([tagName, tagValue])
94+
})
95+
3696
Then(/(\w+) receives (\d+) parameterized_replaceable_event_0 events? from (\w+) with content "([^"]+?)" and EOSE/, async function(
3797
name: string,
3898
count: string,

test/integration/features/shared.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@ import Sinon from 'sinon'
1616
import { connect, createIdentity, createSubscription, sendEvent } from './helpers'
1717
import { getMasterDbClient, getReadReplicaDbClient } from '../../../src/database/client'
1818
import { AppWorker } from '../../../src/app/worker'
19-
import { CacheClient } from '../../../src/@types/cache'
2019
import { DatabaseClient } from '../../../src/@types/base'
2120
import { Event } from '../../../src/@types/event'
22-
import { getCacheClient } from '../../../src/cache/client'
2321
import { SettingsStatic } from '../../../src/utils/settings'
2422
import { workerFactory } from '../../../src/factories/worker-factory'
2523

@@ -29,14 +27,12 @@ let worker: AppWorker
2927

3028
let dbClient: DatabaseClient
3129
let rrDbClient: DatabaseClient
32-
let cacheClient: CacheClient
3330

3431
export const streams = new WeakMap<WebSocket, Observable<unknown>>()
3532

3633
BeforeAll({ timeout: 1000 }, async function () {
3734
process.env.RELAY_PORT = '18808'
3835
process.env.SECRET = Math.random().toString().repeat(6)
39-
cacheClient = getCacheClient()
4036
dbClient = getMasterDbClient()
4137
rrDbClient = getReadReplicaDbClient()
4238
await dbClient.raw('SELECT 1=1')
@@ -58,7 +54,7 @@ BeforeAll({ timeout: 1000 }, async function () {
5854

5955
AfterAll(async function() {
6056
worker.close(async () => {
61-
await Promise.all([cacheClient.disconnect(), dbClient.destroy(), rrDbClient.destroy()])
57+
await Promise.all([dbClient.destroy(), rrDbClient.destroy()])
6258
})
6359
})
6460

0 commit comments

Comments
 (0)