Skip to content

Commit 93d2a4c

Browse files
test(nip56): cover WoT-weighted content reporting
Signed-off-by: Priyanshubhartistm <bhartipriyanshustm@gmail.com>
1 parent 77206bd commit 93d2a4c

8 files changed

Lines changed: 565 additions & 0 deletions

File tree

‎test/unit/factories/event-strategy-factory.spec.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
IDvmJobRepository,
55
IEventRepository,
66
IInviteCodeRepository,
7+
IReportRepository,
78
IUserRepository,
89
} from '../../../src/@types/repositories'
910
import { ContactListEventStrategy } from '../../../src/handlers/event-strategies/contact-list-event-strategy'
@@ -24,6 +25,7 @@ import { JoinRequestEventStrategy } from '../../../src/handlers/event-strategies
2425
import { LeaveRequestEventStrategy } from '../../../src/handlers/event-strategies/leave-request-event-strategy'
2526
import { ParameterizedReplaceableEventStrategy } from '../../../src/handlers/event-strategies/parameterized-replaceable-event-strategy'
2627
import { ReplaceableEventStrategy } from '../../../src/handlers/event-strategies/replaceable-event-strategy'
28+
import { ReportEventStrategy } from '../../../src/handlers/event-strategies/report-event-strategy'
2729
import { Settings } from '../../../src/@types/settings'
2830
import { TimestampEventStrategy } from '../../../src/handlers/event-strategies/timestamp-event-strategy'
2931
import { VanishEventStrategy } from '../../../src/handlers/event-strategies/vanish-event-strategy'
@@ -33,6 +35,7 @@ describe('eventStrategyFactory', () => {
3335
let userRepository: IUserRepository
3436
let inviteCodeRepository: IInviteCodeRepository
3537
let dvmJobRepository: IDvmJobRepository
38+
let reportRepository: IReportRepository
3639
let cache: ICacheAdapter
3740
let settings: () => Settings
3841
let event: Event
@@ -44,6 +47,7 @@ describe('eventStrategyFactory', () => {
4447
userRepository = {} as any
4548
inviteCodeRepository = {} as any
4649
dvmJobRepository = {} as any
50+
reportRepository = {} as any
4751
cache = {} as any
4852
settings = () => ({ info: { relay_url: 'wss://test.relay' }, wot: { enabled: false } }) as any
4953
event = {} as any
@@ -54,6 +58,7 @@ describe('eventStrategyFactory', () => {
5458
userRepository,
5559
inviteCodeRepository,
5660
dvmJobRepository,
61+
reportRepository,
5762
cache,
5863
settings,
5964
)
@@ -182,4 +187,9 @@ describe('eventStrategyFactory', () => {
182187
event.kind = EventKinds.HANDLER_INFORMATION
183188
expect(factory([event, adapter])).to.be.an.instanceOf(ParameterizedReplaceableEventStrategy)
184189
})
190+
191+
it('returns ReportEventStrategy given a report event (NIP-56, kind 1984)', () => {
192+
event.kind = EventKinds.REPORT
193+
expect(factory([event, adapter])).to.be.an.instanceOf(ReportEventStrategy)
194+
})
185195
})

‎test/unit/factories/message-handler-factory.spec.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
IEventRepository,
66
IInviteCodeRepository,
77
INip05VerificationRepository,
8+
IReportRepository,
89
IUserRepository,
910
} from '../../../src/@types/repositories'
1011
import { IncomingMessage, MessageType } from '../../../src/@types/messages'
@@ -26,6 +27,7 @@ describe('messageHandlerFactory', () => {
2627
let nip05VerificationRepository: INip05VerificationRepository
2728
let inviteCodeRepository: IInviteCodeRepository
2829
let dvmJobRepository: IDvmJobRepository
30+
let reportRepository: IReportRepository
2931
let message: IncomingMessage
3032
let adapter: IWebSocketAdapter
3133
let factory
@@ -50,6 +52,7 @@ describe('messageHandlerFactory', () => {
5052
nip05VerificationRepository = {} as any
5153
inviteCodeRepository = {} as any
5254
dvmJobRepository = {} as any
55+
reportRepository = {} as any
5356
adapter = {} as any
5457
event = {
5558
tags: [],
@@ -60,6 +63,7 @@ describe('messageHandlerFactory', () => {
6063
nip05VerificationRepository,
6164
inviteCodeRepository,
6265
dvmJobRepository,
66+
reportRepository,
6367
)
6468
})
6569

‎test/unit/factories/websocket-adapter-factory.spec.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
IEventRepository,
99
IInviteCodeRepository,
1010
INip05VerificationRepository,
11+
IReportRepository,
1112
IUserRepository,
1213
} from '../../../src/@types/repositories'
1314
import { IWebSocketServerAdapter } from '../../../src/@types/adapters'
@@ -40,6 +41,7 @@ describe('webSocketAdapterFactory', () => {
4041
const nip05VerificationRepository: INip05VerificationRepository = {} as any
4142
const inviteCodeRepository: IInviteCodeRepository = {} as any
4243
const dvmJobRepository: IDvmJobRepository = {} as any
44+
const reportRepository: IReportRepository = {} as any
4345

4446
const client: WebSocket = {
4547
on: onStub,
@@ -61,6 +63,7 @@ describe('webSocketAdapterFactory', () => {
6163
nip05VerificationRepository,
6264
inviteCodeRepository,
6365
dvmJobRepository,
66+
reportRepository,
6467
)
6568
expect(factory([client, request, webSocketServerAdapter])).to.be.an.instanceOf(WebSocketAdapter)
6669
})
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import chai from 'chai'
2+
import chaiAsPromised from 'chai-as-promised'
3+
import Sinon from 'sinon'
4+
5+
chai.use(chaiAsPromised)
6+
7+
const { expect } = chai
8+
9+
import { Event } from '../../../../src/@types/event'
10+
import { IEventRepository, IReportRepository } from '../../../../src/@types/repositories'
11+
import { IEventStrategy } from '../../../../src/@types/message-handlers'
12+
import { IWebSocketAdapter } from '../../../../src/@types/adapters'
13+
import { IWotGraphService } from '../../../../src/@types/services'
14+
import { MessageType } from '../../../../src/@types/messages'
15+
import { ReportEventStrategy } from '../../../../src/handlers/event-strategies/report-event-strategy'
16+
import { ReportType } from '../../../../src/@types/report'
17+
import { Settings } from '../../../../src/@types/settings'
18+
import { WebSocketAdapterEvent } from '../../../../src/constants/adapter'
19+
20+
describe('ReportEventStrategy', () => {
21+
const reporterPubkey = '2'.repeat(64)
22+
const reportedPubkey = '3'.repeat(64)
23+
24+
const event: Event = {
25+
id: 'event-id',
26+
pubkey: reporterPubkey,
27+
kind: 1984,
28+
tags: [['p', reportedPubkey, 'spam']],
29+
} as any
30+
31+
let webSocket: IWebSocketAdapter
32+
let eventRepository: IEventRepository
33+
let reportRepository: IReportRepository
34+
let wotGraphService: IWotGraphService
35+
let settings: () => Settings
36+
37+
let webSocketEmitStub: Sinon.SinonStub
38+
let eventRepositoryCreateStub: Sinon.SinonStub
39+
let reportRepositoryCreateStub: Sinon.SinonStub
40+
let getDistanceStub: Sinon.SinonStub
41+
42+
let strategy: IEventStrategy<Event, Promise<void>>
43+
44+
let sandbox: Sinon.SinonSandbox
45+
46+
beforeEach(() => {
47+
sandbox = Sinon.createSandbox()
48+
49+
webSocketEmitStub = sandbox.stub()
50+
webSocket = {
51+
emit: webSocketEmitStub,
52+
} as any
53+
54+
eventRepositoryCreateStub = sandbox.stub()
55+
eventRepository = {
56+
create: eventRepositoryCreateStub,
57+
} as any
58+
59+
reportRepositoryCreateStub = sandbox.stub()
60+
reportRepository = {
61+
create: reportRepositoryCreateStub,
62+
} as any
63+
64+
getDistanceStub = sandbox.stub()
65+
wotGraphService = {
66+
getDistance: getDistanceStub,
67+
} as any
68+
69+
settings = () => ({ nip56: { enabled: true, trustedModerators: [] } }) as any
70+
71+
strategy = new ReportEventStrategy(webSocket, eventRepository, reportRepository, wotGraphService, settings)
72+
})
73+
74+
afterEach(() => {
75+
sandbox.restore()
76+
})
77+
78+
describe('execute', () => {
79+
it('creates the event', async () => {
80+
eventRepositoryCreateStub.resolves(1)
81+
reportRepositoryCreateStub.resolves({})
82+
getDistanceStub.resolves(1)
83+
84+
await strategy.execute(event)
85+
86+
expect(eventRepositoryCreateStub).to.have.been.calledOnceWithExactly(event)
87+
})
88+
89+
it('broadcasts the event when newly created', async () => {
90+
eventRepositoryCreateStub.resolves(1)
91+
reportRepositoryCreateStub.resolves({})
92+
getDistanceStub.resolves(1)
93+
94+
await strategy.execute(event)
95+
96+
expect(webSocketEmitStub).to.have.been.calledWithExactly(WebSocketAdapterEvent.Message, [
97+
MessageType.OK,
98+
'event-id',
99+
true,
100+
'',
101+
])
102+
expect(webSocketEmitStub).to.have.been.calledWithExactly(WebSocketAdapterEvent.Broadcast, event)
103+
})
104+
105+
it('does not broadcast or record a report when the event is a duplicate', async () => {
106+
eventRepositoryCreateStub.resolves(0)
107+
108+
await strategy.execute(event)
109+
110+
expect(webSocketEmitStub).to.have.been.calledOnceWithExactly(WebSocketAdapterEvent.Message, [
111+
MessageType.OK,
112+
'event-id',
113+
true,
114+
'duplicate:',
115+
])
116+
expect(reportRepositoryCreateStub).not.to.have.been.called
117+
})
118+
119+
it('records a report with full weight for a direct follow (distance 1)', async () => {
120+
eventRepositoryCreateStub.resolves(1)
121+
reportRepositoryCreateStub.resolves({})
122+
getDistanceStub.resolves(1)
123+
124+
await strategy.execute(event)
125+
126+
expect(reportRepositoryCreateStub).to.have.been.calledOnceWithExactly({
127+
id: 'event-id',
128+
reporterPubkey,
129+
reportedPubkey,
130+
reportedEventId: null,
131+
reportType: ReportType.SPAM,
132+
weight: 1,
133+
actionable: false,
134+
})
135+
})
136+
137+
it('records a report with zero weight for a reporter outside the trust graph', async () => {
138+
eventRepositoryCreateStub.resolves(1)
139+
reportRepositoryCreateStub.resolves({})
140+
getDistanceStub.resolves(undefined)
141+
142+
await strategy.execute(event)
143+
144+
expect(reportRepositoryCreateStub).to.have.been.calledOnceWithExactly({
145+
id: 'event-id',
146+
reporterPubkey,
147+
reportedPubkey,
148+
reportedEventId: null,
149+
reportType: ReportType.SPAM,
150+
weight: 0,
151+
actionable: false,
152+
})
153+
})
154+
155+
it('records an actionable, max-weight report from a trusted moderator regardless of distance', async () => {
156+
settings = () => ({ nip56: { enabled: true, trustedModerators: [reporterPubkey] } }) as any
157+
strategy = new ReportEventStrategy(webSocket, eventRepository, reportRepository, wotGraphService, settings)
158+
eventRepositoryCreateStub.resolves(1)
159+
reportRepositoryCreateStub.resolves({})
160+
getDistanceStub.resolves(undefined)
161+
162+
await strategy.execute(event)
163+
164+
expect(reportRepositoryCreateStub).to.have.been.calledOnceWithExactly({
165+
id: 'event-id',
166+
reporterPubkey,
167+
reportedPubkey,
168+
reportedEventId: null,
169+
reportType: ReportType.SPAM,
170+
weight: 1,
171+
actionable: true,
172+
})
173+
})
174+
175+
it('stores the event but does not record a report when nip56 is disabled', async () => {
176+
settings = () => ({ nip56: { enabled: false, trustedModerators: [] } }) as any
177+
strategy = new ReportEventStrategy(webSocket, eventRepository, reportRepository, wotGraphService, settings)
178+
eventRepositoryCreateStub.resolves(1)
179+
180+
await strategy.execute(event)
181+
182+
expect(eventRepositoryCreateStub).to.have.been.calledOnceWithExactly(event)
183+
expect(webSocketEmitStub).to.have.been.calledWithExactly(WebSocketAdapterEvent.Broadcast, event)
184+
expect(reportRepositoryCreateStub).not.to.have.been.called
185+
expect(getDistanceStub).not.to.have.been.called
186+
})
187+
188+
it('does not reject the event when report recording fails', async () => {
189+
eventRepositoryCreateStub.resolves(1)
190+
getDistanceStub.resolves(1)
191+
reportRepositoryCreateStub.rejects(new Error('db unavailable'))
192+
193+
await expect(strategy.execute(event)).to.eventually.be.fulfilled
194+
195+
expect(webSocketEmitStub).to.have.been.calledWithExactly(WebSocketAdapterEvent.Message, [
196+
MessageType.OK,
197+
'event-id',
198+
true,
199+
'',
200+
])
201+
})
202+
203+
it('rejects if unable to create the event', async () => {
204+
const error = new Error('event creation failed')
205+
eventRepositoryCreateStub.rejects(error)
206+
207+
await expect(strategy.execute(event)).to.eventually.be.rejectedWith(error)
208+
209+
expect(reportRepositoryCreateStub).not.to.have.been.called
210+
})
211+
})
212+
})

0 commit comments

Comments
 (0)