test: add unit test for factories

This commit is contained in:
Ricardo Arturo Cabral Mejía 2022-11-10 23:14:41 -05:00
parent 9cea3bec4e
commit e50b4952b6
11 changed files with 279 additions and 12 deletions

View File

@ -1,6 +1,6 @@
export enum EventKinds {
SET_METADATA = 0,
TEXT_NODE = 1,
TEXT_NOTE = 1,
RECOMMEND_SERVER = 2,
CONTACT_LIST = 3,
ENCRYPTED_DIRECT_MESSAGE = 4,
@ -10,6 +10,8 @@ export enum EventKinds {
REPLACEABLE_LAST = 19999,
EPHEMERAL_FIRST = 20000,
EPHEMERAL_LAST = 29999,
PARAMETERIZED_REPLACEABLE_FIRST = 30000,
PARAMETERIZED_REPLACEABLE_LAST = 39999,
}
export enum EventTags {

View File

@ -31,6 +31,6 @@ export const messageHandlerFactory = (
case MessageType.CLOSE:
return new UnsubscribeMessageHandler(adapter,)
default:
throw new Error(`Unknown message type: ${String(message[0])}`)
throw new Error(`Unknown message type: ${String(message[0]).substring(0, 64)}`)
}
}

View File

@ -222,11 +222,11 @@ export class EventRepository implements IEventRepository {
.merge(omit(['event_pubkey', 'event_kind', 'event_deduplication'])(row))
.where('events.event_created_at', '<', row.event_created_at)
const promise = query.then(prop('rowCount') as () => number)
promise.toString = () => query.toString()
return promise
return {
then: <T1, T2>(onfulfilled: (value: number) => T1 | PromiseLike<T1>, onrejected: (reason: any) => T2 | PromiseLike<T2>) => query.then(prop('rowCount') as () => number).then(onfulfilled, onrejected),
catch: <T>(onrejected: (reason: any) => T | PromiseLike<T>) => query.catch(onrejected),
toString: (): string => query.toString(),
} as Promise<number>
}
public deleteByPubkeyAndIds(pubkey: string, ids: EventId[]): Promise<number> {

View File

@ -167,15 +167,18 @@ export const isEventSignatureValid = async (event: Event): Promise<boolean> => {
}
export const isReplaceableEvent = (event: Event): boolean => {
return event.kind === 0 || event.kind === 3 || (event.kind >= 10000 && event.kind < 20000)
return event.kind === EventKinds.SET_METADATA
|| event.kind === EventKinds.CONTACT_LIST
|| (event.kind >= EventKinds.REPLACEABLE_FIRST && event.kind <= EventKinds.REPLACEABLE_LAST)
}
export const isEphemeralEvent = (event: Event): boolean => {
return event.kind >= 20000 && event.kind < 30000
return event.kind >= EventKinds.EPHEMERAL_FIRST && event.kind <= EventKinds.EPHEMERAL_LAST
}
export const isParameterizedReplaceableEvent = (event: Event): boolean => {
return event.kind >= 30000 && event.kind < 40000
return event.kind >= EventKinds.PARAMETERIZED_REPLACEABLE_FIRST
&& event.kind <= EventKinds.PARAMETERIZED_REPLACEABLE_LAST
}
export const isDeleteEvent = (event: Event): boolean => {

View File

@ -0,0 +1,10 @@
import { expect } from 'chai'
import { App } from '../../../src/app/app'
import { appFactory } from '../../../src/factories/app-factory'
describe('appFactory', () => {
it('returns an App', () => {
expect(appFactory()).to.be.an.instanceOf(App)
})
})

View File

@ -0,0 +1,46 @@
import { expect } from 'chai'
import { DefaultEventStrategy } from '../../../src/handlers/event-strategies/default-event-strategy'
import { delegatedEventStrategyFactory } from '../../../src/factories/delegated-event-strategy-factory'
import { EphemeralEventStrategy } from '../../../src/handlers/event-strategies/ephemeral-event-strategy'
import { Event } from '../../../src/@types/event'
import { EventKinds } from '../../../src/constants/base'
import { Factory } from '../../../src/@types/base'
import { IEventRepository } from '../../../src/@types/repositories'
import { IEventStrategy } from '../../../src/@types/message-handlers'
import { IWebSocketAdapter } from '../../../src/@types/adapters'
describe('delegatedEventStrategyFactory', () => {
let eventRepository: IEventRepository
let event: Event
let adapter: IWebSocketAdapter
let factory: Factory<IEventStrategy<Event, Promise<void>>, [Event, IWebSocketAdapter]>
beforeEach(() => {
eventRepository = {} as any
event = {} as any
adapter = {} as any
factory = delegatedEventStrategyFactory(eventRepository)
})
it('returns EphemeralEventStrategy given a set_metadata event', () => {
event.kind = EventKinds.EPHEMERAL_FIRST
expect(factory([event, adapter])).to.be.an.instanceOf(EphemeralEventStrategy)
})
it('returns DefaultEventStrategy given a text_note event', () => {
event.kind = EventKinds.TEXT_NOTE
expect(factory([event, adapter])).to.be.an.instanceOf(DefaultEventStrategy)
})
it('returns undefined given a replaceable event', () => {
event.kind = EventKinds.REPLACEABLE_FIRST
expect(factory([event, adapter])).to.be.undefined
})
it('returns undefined given a delete event', () => {
event.kind = EventKinds.DELETE
expect(factory([event, adapter])).to.be.undefined
})
})

View File

@ -0,0 +1,64 @@
import { expect } from 'chai'
import { DefaultEventStrategy } from '../../../src/handlers/event-strategies/default-event-strategy'
import { DeleteEventStrategy } from '../../../src/handlers/event-strategies/delete-event-strategy'
import { EphemeralEventStrategy } from '../../../src/handlers/event-strategies/ephemeral-event-strategy'
import { Event } from '../../../src/@types/event'
import { EventKinds } from '../../../src/constants/base'
import { eventStrategyFactory } from '../../../src/factories/event-strategy-factory'
import { Factory } from '../../../src/@types/base'
import { IEventRepository } from '../../../src/@types/repositories'
import { IEventStrategy } from '../../../src/@types/message-handlers'
import { IWebSocketAdapter } from '../../../src/@types/adapters'
import { ParameterizedReplaceableEventStrategy } from '../../../src/handlers/event-strategies/parameterized-replaceable-event-strategy'
import { ReplaceableEventStrategy } from '../../../src/handlers/event-strategies/replaceable-event-strategy'
describe('eventStrategyFactory', () => {
let eventRepository: IEventRepository
let event: Event
let adapter: IWebSocketAdapter
let factory: Factory<IEventStrategy<Event, Promise<void>>, [Event, IWebSocketAdapter]>
beforeEach(() => {
eventRepository = {} as any
event = {} as any
adapter = {} as any
factory = eventStrategyFactory(eventRepository)
})
it('returns ReplaceableEvent given a set_metadata event', () => {
event.kind = EventKinds.SET_METADATA
expect(factory([event, adapter])).to.be.an.instanceOf(ReplaceableEventStrategy)
})
it('returns ReplaceableEvent given a contact_list event', () => {
event.kind = EventKinds.CONTACT_LIST
expect(factory([event, adapter])).to.be.an.instanceOf(ReplaceableEventStrategy)
})
it('returns ReplaceableEvent given a replaceable event', () => {
event.kind = EventKinds.REPLACEABLE_FIRST
expect(factory([event, adapter])).to.be.an.instanceOf(ReplaceableEventStrategy)
})
it('returns EphemeralEventStrategy given an ephemeral event', () => {
event.kind = EventKinds.EPHEMERAL_FIRST
expect(factory([event, adapter])).to.be.an.instanceOf(EphemeralEventStrategy)
})
it('returns DeleteEventStrategy given a delete event', () => {
event.kind = EventKinds.DELETE
expect(factory([event, adapter])).to.be.an.instanceOf(DeleteEventStrategy)
})
it('returns ParameterizedReplaceableEventStrategy given a delete event', () => {
event.kind = EventKinds.PARAMETERIZED_REPLACEABLE_FIRST
expect(factory([event, adapter])).to.be.an.instanceOf(ParameterizedReplaceableEventStrategy)
})
it('returns DefaultEventStrategy given a text_note event', () => {
event.kind = EventKinds.TEXT_NOTE
expect(factory([event, adapter])).to.be.an.instanceOf(DefaultEventStrategy)
})
})

View File

@ -0,0 +1,77 @@
import { expect } from 'chai'
import { IncomingMessage, 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 { EventTags } from '../../../src/constants/base'
import { IEventRepository } from '../../../src/@types/repositories'
import { IWebSocketAdapter } from '../../../src/@types/adapters'
import { messageHandlerFactory } from '../../../src/factories/message-handler-factory'
import { SubscribeMessageHandler } from '../../../src/handlers/subscribe-message-handler'
import { UnsubscribeMessageHandler } from '../../../src/handlers/unsubscribe-message-handler'
describe('messageHandlerFactory', () => {
let event: Event
let eventRepository: IEventRepository
let message: IncomingMessage
let adapter: IWebSocketAdapter
let factory
beforeEach(() => {
eventRepository = {} as any
adapter = {} as any
event = {
tags: [],
} as any
factory = messageHandlerFactory(eventRepository)
})
it('returns EventMessageHandler when given an EVENT message', () => {
message = [
MessageType.EVENT,
event,
]
expect(factory([message, adapter])).to.be.an.instanceOf(EventMessageHandler)
})
it('returns DelegatedEventMessageHandler when given an EVENT message with delegated event', () => {
event.tags = [
[EventTags.Delegation, '', '', ''],
]
message = [
MessageType.EVENT,
event,
]
expect(factory([message, adapter])).to.be.an.instanceOf(DelegatedEventMessageHandler)
})
it('returns SubscribeMessageHandler when given a REQ message', () => {
message = [
MessageType.REQ,
'',
{},
] as any
expect(factory([message, adapter])).to.be.an.instanceOf(SubscribeMessageHandler)
})
it('returns UnsubscribeMessageHandler when given a REQ message', () => {
message = [
MessageType.CLOSE,
'',
]
expect(factory([message, adapter])).to.be.an.instanceOf(UnsubscribeMessageHandler)
})
it('throws when given an invalid message', () => {
message = []
expect(() => factory([message, adapter])).to.throw(Error, 'Unknown message type: undefined')
})
})

View File

@ -0,0 +1,40 @@
import { expect } from 'chai'
import { IncomingMessage } from 'http'
import Sinon from 'sinon'
import WebSocket from 'ws'
import { IEventRepository } from '../../../src/@types/repositories'
import { IWebSocketServerAdapter } from '../../../src/@types/adapters'
import { WebSocketAdapter } from '../../../src/adapters/web-socket-adapter'
import { webSocketAdapterFactory } from '../../../src/factories/websocket-adapter-factory'
describe('webSocketAdapterFactory', () => {
let onStub: Sinon.SinonStub
beforeEach(() => {
onStub = Sinon.stub()
})
afterEach(() => {
onStub.reset()
})
it('returns a WebSocketAdapter', () => {
const eventRepository: IEventRepository = {} as any
const client: WebSocket = {
on: onStub,
} as any
onStub.returns(client)
const request: IncomingMessage = {
headers: {
'sec-websocket-key': Buffer.from('key', 'utf-8').toString('base64'),
},
} as any
const webSocketServerAdapter: IWebSocketServerAdapter = {} as any
expect(
webSocketAdapterFactory(eventRepository)([client, request, webSocketServerAdapter])
).to.be.an.instanceOf(WebSocketAdapter)
})
})

View File

@ -0,0 +1,25 @@
import { expect } from 'chai'
import Sinon from 'sinon'
import * as databaseClientModule from '../../../src/database/client'
import { AppWorker } from '../../../src/app/worker'
import { workerFactory } from '../../../src/factories/worker-factory'
describe('workerFactory', () => {
let getDbClientStub: Sinon.SinonStub
beforeEach(() => {
getDbClientStub = Sinon.stub(databaseClientModule, 'getDbClient')
})
afterEach(() => {
getDbClientStub.restore()
})
it('returns an AppWorker', () => {
const worker = workerFactory()
expect(worker).to.be.an.instanceOf(AppWorker)
worker.close()
})
})

View File

@ -21,7 +21,7 @@ describe('NIP-01', () => {
const event: Partial<Event> = {
pubkey: 'pubkey',
created_at: 1000,
kind: EventKinds.TEXT_NODE,
kind: EventKinds.TEXT_NOTE,
tags: [['tag name', 'tag content']],
content: 'content',
}
@ -30,7 +30,7 @@ describe('NIP-01', () => {
0,
'pubkey',
1000,
EventKinds.TEXT_NODE,
EventKinds.TEXT_NOTE,
[['tag name', 'tag content']],
'content',
]