Skip to content

Commit fddb3a0

Browse files
test: add NIP-17 gift wrap integration tests
1 parent f5093cb commit fddb3a0

2 files changed

Lines changed: 213 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Feature: NIP-17 Gift wrap validation
2+
Scenario: Anshuman publishes a valid gift wrap with one recipient
3+
Given someone called Anshuman
4+
And someone called Bob
5+
When Anshuman sends a valid gift_wrap event for Bob
6+
Then Anshuman receives a successful gift_wrap command result
7+
8+
Scenario: Anshuman cannot publish a gift wrap without a recipient p tag
9+
Given someone called Anshuman
10+
When Anshuman sends an invalid gift_wrap event without a p tag
11+
Then Anshuman receives an unsuccessful gift_wrap command result with reason containing "invalid: gift wrap event (kind 1059) must have a p tag identifying the recipient"
12+
13+
Scenario: Anshuman cannot publish a gift wrap with multiple recipient p tags
14+
Given someone called Anshuman
15+
And someone called Bob
16+
And someone called Charlie
17+
When Anshuman sends an invalid gift_wrap event with recipients Bob and Charlie
18+
Then Anshuman receives an unsuccessful gift_wrap command result with reason containing "invalid: gift wrap event (kind 1059) must have exactly one p tag"
19+
20+
Scenario: Anshuman cannot publish a gift wrap with malformed NIP-44 payload
21+
Given someone called Anshuman
22+
And someone called Bob
23+
When Anshuman sends an invalid gift_wrap event for Bob with malformed NIP-44 payload
24+
Then Anshuman receives an unsuccessful gift_wrap command result with reason containing "invalid: gift wrap content must be a valid NIP-44 v2 payload"
25+
26+
Scenario: Bob can query gift wraps addressed to him via #p
27+
Given someone called Anshuman
28+
And someone called Bob
29+
And someone called Charlie
30+
When Anshuman sends a valid gift_wrap event for Bob
31+
And Anshuman sends a valid gift_wrap event for Charlie
32+
And Bob subscribes to gift_wrap events tagged for Bob
33+
Then Bob receives 1 gift_wrap event from Anshuman tagged for Bob and EOSE
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { Then, When, World } from '@cucumber/cucumber'
2+
import { expect } from 'chai'
3+
import WebSocket from 'ws'
4+
5+
import { EventKinds, EventTags } from '../../../../src/constants/base'
6+
import { getConversationKey, nip44Encrypt } from '../../../../src/utils/nip44'
7+
import { createEvent, createSubscription, sendEvent, waitForEventCount } from '../helpers'
8+
import { Event } from '../../../../src/@types/event'
9+
import { CommandResult } from '../../../../src/@types/messages'
10+
11+
const ensureNip17State = (world: World<Record<string, any>>) => {
12+
world.parameters.nip17 = world.parameters.nip17 ?? {}
13+
world.parameters.nip17.results = world.parameters.nip17.results ?? {}
14+
}
15+
16+
const storeResult = (world: World<Record<string, any>>, name: string, result: { success: boolean; reason?: string }) => {
17+
ensureNip17State(world)
18+
world.parameters.nip17.results[name] = result
19+
}
20+
21+
const sendEventExpectFailure = async (ws: WebSocket, event: Event): Promise<string> => {
22+
try {
23+
await sendEvent(ws, event, true)
24+
} catch (error) {
25+
return (error as Error).message
26+
}
27+
28+
throw new Error('Expected gift wrap publication to fail, but it succeeded')
29+
}
30+
31+
const makeNip44Payload = (senderPrivkey: string, recipientPubkey: string): string => {
32+
const conversationKey = getConversationKey(senderPrivkey, recipientPubkey)
33+
return nip44Encrypt('{"kind":13,"content":"sealed"}', conversationKey)
34+
}
35+
36+
When(
37+
/^(\w+) sends a valid gift_wrap event for (\w+)$/,
38+
async function (this: World<Record<string, any>>, sender: string, recipient: string) {
39+
const ws = this.parameters.clients[sender] as WebSocket
40+
const { pubkey, privkey } = this.parameters.identities[sender]
41+
const recipientPubkey = this.parameters.identities[recipient].pubkey
42+
43+
const event: Event = await createEvent(
44+
{
45+
pubkey,
46+
kind: EventKinds.GIFT_WRAP,
47+
tags: [[EventTags.Pubkey, recipientPubkey]],
48+
content: makeNip44Payload(privkey, recipientPubkey),
49+
},
50+
privkey,
51+
)
52+
53+
const command = (await sendEvent(ws, event, true)) as CommandResult
54+
55+
this.parameters.events[sender].push(event)
56+
storeResult(this, sender, { success: command[2], reason: command[3] })
57+
},
58+
)
59+
60+
When(
61+
/^(\w+) sends an invalid gift_wrap event without a p tag$/,
62+
async function (this: World<Record<string, any>>, sender: string) {
63+
const ws = this.parameters.clients[sender] as WebSocket
64+
const { pubkey, privkey } = this.parameters.identities[sender]
65+
66+
const event: Event = await createEvent(
67+
{
68+
pubkey,
69+
kind: EventKinds.GIFT_WRAP,
70+
tags: [],
71+
content: makeNip44Payload(privkey, pubkey),
72+
},
73+
privkey,
74+
)
75+
76+
const reason = await sendEventExpectFailure(ws, event)
77+
storeResult(this, sender, { success: false, reason })
78+
},
79+
)
80+
81+
When(
82+
/^(\w+) sends an invalid gift_wrap event with recipients (\w+) and (\w+)$/,
83+
async function (this: World<Record<string, any>>, sender: string, recipient1: string, recipient2: string) {
84+
const ws = this.parameters.clients[sender] as WebSocket
85+
const { pubkey, privkey } = this.parameters.identities[sender]
86+
const recipientPubkey1 = this.parameters.identities[recipient1].pubkey
87+
const recipientPubkey2 = this.parameters.identities[recipient2].pubkey
88+
89+
const event: Event = await createEvent(
90+
{
91+
pubkey,
92+
kind: EventKinds.GIFT_WRAP,
93+
tags: [
94+
[EventTags.Pubkey, recipientPubkey1],
95+
[EventTags.Pubkey, recipientPubkey2],
96+
],
97+
content: makeNip44Payload(privkey, recipientPubkey1),
98+
},
99+
privkey,
100+
)
101+
102+
const reason = await sendEventExpectFailure(ws, event)
103+
storeResult(this, sender, { success: false, reason })
104+
},
105+
)
106+
107+
When(
108+
/^(\w+) sends an invalid gift_wrap event for (\w+) with malformed NIP-44 payload$/,
109+
async function (this: World<Record<string, any>>, sender: string, recipient: string) {
110+
const ws = this.parameters.clients[sender] as WebSocket
111+
const { pubkey, privkey } = this.parameters.identities[sender]
112+
const recipientPubkey = this.parameters.identities[recipient].pubkey
113+
114+
const event: Event = await createEvent(
115+
{
116+
pubkey,
117+
kind: EventKinds.GIFT_WRAP,
118+
tags: [[EventTags.Pubkey, recipientPubkey]],
119+
content: 'this is not encrypted',
120+
},
121+
privkey,
122+
)
123+
124+
const reason = await sendEventExpectFailure(ws, event)
125+
storeResult(this, sender, { success: false, reason })
126+
},
127+
)
128+
129+
When(
130+
/^(\w+) subscribes to gift_wrap events tagged for (\w+)$/,
131+
async function (this: World<Record<string, any>>, name: string, recipient: string) {
132+
const ws = this.parameters.clients[name] as WebSocket
133+
const recipientPubkey = this.parameters.identities[recipient].pubkey
134+
const subscription = {
135+
name: `test-${Math.random()}`,
136+
filters: [{ kinds: [EventKinds.GIFT_WRAP], '#p': [recipientPubkey] }],
137+
}
138+
139+
this.parameters.subscriptions[name].push(subscription)
140+
141+
await createSubscription(ws, subscription.name, subscription.filters)
142+
},
143+
)
144+
145+
Then(/^(\w+) receives a successful gift_wrap command result$/, function (this: World<Record<string, any>>, name: string) {
146+
const result = this.parameters.nip17.results[name] as { success: boolean; reason?: string }
147+
148+
expect(result.success).to.equal(true)
149+
expect(result.reason).to.equal('')
150+
})
151+
152+
Then(
153+
/^(\w+) receives an unsuccessful gift_wrap command result with reason containing "([^"]+)"$/,
154+
function (this: World<Record<string, any>>, name: string, reasonPart: string) {
155+
const result = this.parameters.nip17.results[name] as { success: boolean; reason?: string }
156+
157+
expect(result.success).to.equal(false)
158+
expect(result.reason).to.contain(reasonPart)
159+
},
160+
)
161+
162+
Then(
163+
/^(\w+) receives (\d+) gift_wrap events? from (\w+) tagged for (\w+) and EOSE$/,
164+
async function (this: World<Record<string, any>>, name: string, count: string, author: string, recipient: string) {
165+
const ws = this.parameters.clients[name] as WebSocket
166+
const subscription = this.parameters.subscriptions[name][this.parameters.subscriptions[name].length - 1]
167+
const expectedCount = Number(count)
168+
const events = await waitForEventCount(ws, subscription.name, expectedCount, true)
169+
170+
expect(events.length).to.equal(expectedCount)
171+
expect(events[0].kind).to.equal(EventKinds.GIFT_WRAP)
172+
expect(events[0].pubkey).to.equal(this.parameters.identities[author].pubkey)
173+
174+
const recipientPubkey = this.parameters.identities[recipient].pubkey
175+
const recipientTags = events[0].tags.filter((tag) => tag.length >= 2 && tag[0] === EventTags.Pubkey)
176+
177+
expect(recipientTags.length).to.equal(1)
178+
expect(recipientTags[0][1]).to.equal(recipientPubkey)
179+
},
180+
)

0 commit comments

Comments
 (0)