From d65da0113e0798ee072d7d4c7da726a27a867c93 Mon Sep 17 00:00:00 2001 From: Ricardo Arturo Cabral Mejia Date: Fri, 21 Oct 2022 17:08:20 -0400 Subject: [PATCH] test: delegated event message handler --- src/@types/event.ts | 3 + .../delegated-event-message-handler.ts | 17 +- .../delegated-event-message-handler.spec.ts | 183 ++++++++++++++++++ 3 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 test/unit/handlers/delegated-event-message-handler.spec.ts diff --git a/src/@types/event.ts b/src/@types/event.ts index 7219b120..8dc15d31 100644 --- a/src/@types/event.ts +++ b/src/@types/event.ts @@ -10,6 +10,9 @@ export interface Event { tags: Tag[] sig: string content: string +} + +export interface DelegatedEvent extends Event { [EventDelegatorMetadataKey]?: Pubkey } diff --git a/src/handlers/delegated-event-message-handler.ts b/src/handlers/delegated-event-message-handler.ts index f0bd4638..5e8080e1 100644 --- a/src/handlers/delegated-event-message-handler.ts +++ b/src/handlers/delegated-event-message-handler.ts @@ -1,6 +1,8 @@ +import { mergeDeepLeft } from 'ramda' + +import { DelegatedEvent, Event } from '../@types/event' import { EventDelegatorMetadataKey, EventTags } from '../constants/base' import { createNoticeMessage } from '../utils/messages' -import { Event } from '../@types/event' import { EventMessageHandler } from './event-message-handler' import { IMessageHandler } from '../@types/message-handlers' import { IncomingEventMessage } from '../@types/messages' @@ -25,23 +27,28 @@ export class DelegatedEventMessageHandler extends EventMessageHandler implements } const [, delegator] = event.tags.find((tag) => tag.length === 4 && tag[0] === EventTags.Delegation) - event[EventDelegatorMetadataKey] = delegator + const delegatedEvent: DelegatedEvent = mergeDeepLeft( + event, + { + [EventDelegatorMetadataKey]: delegator, + } + ) - const strategy = this.strategyFactory([event, this.webSocket]) + const strategy = this.strategyFactory([delegatedEvent, this.webSocket]) if (typeof strategy?.execute !== 'function') { return } try { - await strategy.execute(event) + await strategy.execute(delegatedEvent) } catch (error) { console.error('Error handling message:', message, error) } } protected async isEventValid(event: Event): Promise { - const reason = super.isEventValid(event) + const reason = await super.isEventValid(event) if (reason) { return reason } diff --git a/test/unit/handlers/delegated-event-message-handler.spec.ts b/test/unit/handlers/delegated-event-message-handler.spec.ts new file mode 100644 index 00000000..1db7fe35 --- /dev/null +++ b/test/unit/handlers/delegated-event-message-handler.spec.ts @@ -0,0 +1,183 @@ +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import EventEmitter from 'events' +import Sinon from 'sinon' + +chai.use(chaiAsPromised) + +import { IncomingEventMessage, MessageType } from '../../../src/@types/messages' +import { DelegatedEventMessageHandler } from '../../../src/handlers/delegated-event-message-handler' +import { Event } from '../../../src/@types/event' +import { EventMessageHandler } from '../../../src/handlers/event-message-handler' +import { WebSocketAdapterEvent } from '../../../src/constants/adapter' + +const { expect } = chai + +describe('DelegatedEventMessageHandler', () => { + let webSocket: EventEmitter + let handler: DelegatedEventMessageHandler + let event: Event + let message: IncomingEventMessage + let sandbox: Sinon.SinonSandbox + + let originalConsoleWarn: (message?: any, ...optionalParams: any[]) => void | undefined = undefined + + beforeEach(() => { + sandbox = Sinon.createSandbox() + originalConsoleWarn = console.warn + console.warn = () => undefined + event = { + content: 'hello', + created_at: 1665546189, + id: 'f'.repeat(64), + kind: 1, + pubkey: 'f'.repeat(64), + sig: 'f'.repeat(128), + tags: [ + ['delegation', 'delegator', 'rune', 'signature'], + ], + } + }) + + afterEach(() => { + console.warn = originalConsoleWarn + sandbox.restore() + }) + + describe('handleMessage', () => { + let canAcceptEventStub: Sinon.SinonStub + let isEventValidStub: Sinon.SinonStub + let strategyFactoryStub: Sinon.SinonStub + let onMessageSpy: Sinon.SinonSpy + let strategyExecuteStub: Sinon.SinonStub + + beforeEach(() => { + canAcceptEventStub = sandbox.stub(DelegatedEventMessageHandler.prototype, 'canAcceptEvent' as any) + isEventValidStub = sandbox.stub(DelegatedEventMessageHandler.prototype, 'isEventValid' as any) + strategyExecuteStub = sandbox.stub() + strategyFactoryStub = sandbox.stub().returns({ + execute: strategyExecuteStub, + }) + onMessageSpy = sandbox.fake.returns(undefined) + webSocket = new EventEmitter() + webSocket.on(WebSocketAdapterEvent.Message, onMessageSpy) + message = [MessageType.EVENT, event] + handler = new DelegatedEventMessageHandler( + webSocket as any, + strategyFactoryStub, + () => ({}) as any, + ) + }) + + afterEach(() => { + isEventValidStub.restore() + canAcceptEventStub.restore() + webSocket.removeAllListeners() + }) + + it('rejects event if it can\'t be accepted', async () => { + canAcceptEventStub.returns('reason') + + await handler.handleMessage(message) + + expect(canAcceptEventStub).to.have.been.calledOnceWithExactly(event) + expect(onMessageSpy).to.have.been.calledOnceWithExactly(['NOTICE', 'Event rejected: reason']) + expect(strategyFactoryStub).not.to.have.been.called + }) + + it('rejects event if invalid', async () => { + isEventValidStub.returns('reason') + + await handler.handleMessage(message) + + expect(isEventValidStub).to.have.been.calledOnceWithExactly(event) + expect(onMessageSpy).not.to.have.been.calledOnceWithExactly() + expect(strategyFactoryStub).not.to.have.been.called + }) + + it('does not call strategy if none given', async () => { + isEventValidStub.returns(undefined) + canAcceptEventStub.returns(undefined) + strategyFactoryStub.returns(undefined) + + await handler.handleMessage(message) + + expect(isEventValidStub).to.have.been.calledOnceWithExactly(event) + expect(onMessageSpy).not.to.have.been.calledOnceWithExactly() + expect(strategyFactoryStub).to.have.been.calledOnceWithExactly([ + event, + webSocket, + ]) + expect(strategyExecuteStub).not.to.have.been.called + }) + + it('calls strategy with event', async () => { + isEventValidStub.returns(undefined) + canAcceptEventStub.returns(undefined) + + await handler.handleMessage(message) + + expect(isEventValidStub).to.have.been.calledOnceWithExactly(event) + expect(onMessageSpy).not.to.have.been.calledOnceWithExactly() + expect(strategyFactoryStub).to.have.been.calledOnceWithExactly([ + event, + webSocket, + ]) + expect(strategyExecuteStub).to.have.been.calledOnceWithExactly(event) + }) + + it('does not reject if strategy rejects', async () => { + isEventValidStub.returns(undefined) + canAcceptEventStub.returns(undefined) + strategyExecuteStub.rejects() + + return expect(handler.handleMessage(message)).to.eventually.be.fulfilled + }) + }) + + describe('isEventValid', () => { + let parentIsEventValidStub: Sinon.SinonStub + + beforeEach(() => { + parentIsEventValidStub = Sinon.stub(EventMessageHandler.prototype, 'isEventValid' as any) + event = { + 'id': 'a080fd288b60ac2225ff2e2d815291bd730911e583e177302cc949a15dc2b2dc', + 'pubkey': '62903b1ff41559daf9ee98ef1ae67cc52f301bb5ce26d14baba3052f649c3f49', + 'created_at': 1660896109, + 'kind': 1, + 'tags': [ + [ + 'delegation', + '86f0689bd48dcd19c67a19d994f938ee34f251d8c39976290955ff585f2db42e', + 'kind=1&created_at>1640995200', + 'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1', + ], + ], + 'content': 'Hello world', + 'sig': 'cd4a3cd20dc61dcbc98324de561a07fd23b3d9702115920c0814b5fb822cc5b7c5bcdaf3fa326d24ed50c5b9c8214d66c75bae34e3a84c25e4d122afccb66eb6', + } + }) + + afterEach(() => { + parentIsEventValidStub.restore() + }) + + it('returns undefined if event and delegate tag is valid', async () => { + parentIsEventValidStub.resolves(undefined) + + expect(await (handler as any).isEventValid(event)).to.be.undefined + }) + + it('returns reason if event is not valid', () => { + parentIsEventValidStub.resolves('reason') + return expect((handler as any).isEventValid(event)).to.eventually.equal('reason') + }) + + it('returns reason if delegate signature is not valid', () => { + parentIsEventValidStub.resolves(undefined) + + event.tags[0][3] = 'wrong sig' + return expect((handler as any).isEventValid(event)).to.eventually.equal('Event with id a080fd288b60ac2225ff2e2d815291bd730911e583e177302cc949a15dc2b2dc from 62903b1ff41559daf9ee98ef1ae67cc52f301bb5ce26d14baba3052f649c3f49 is invalid delegated event') + }) + }) +})