Skip to content

Commit

Permalink
test server API
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jan 9, 2020
1 parent ea5c2c9 commit e8fff6a
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 247 deletions.
2 changes: 0 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ module.exports = {
},
coverageReporters: ['text', 'lcov'],
coveragePathIgnorePatterns: [
'test-utils/src/ws-client',
'test-utils/src/utils',
'tests/',
'dist/',
],
Expand Down
52 changes: 26 additions & 26 deletions packages/koishi-core/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ export abstract class Server {
Object.defineProperty(meta, '$ctxType', { value: ctxType })

const app = this.appMap[meta.selfId]
if (!app) return events

// add context properties
if (meta.postType === 'message') {
Expand Down Expand Up @@ -179,8 +178,7 @@ export abstract class Server {
apps[0].prepare(info.userId)
}
} catch (error) {
this.isListening = false
this._close()
this.close()
throw error
}
}
Expand Down Expand Up @@ -253,14 +251,15 @@ export class HttpServer extends Server {
}

async _listen () {
showServerLog('http server opening')
const { port } = this.appList[0].options
this.server.listen(port)
try {
this.version = await this.appList[0].sender.getVersionInfo()
} catch (error) {
throw new Error('authorization failed')
}
showServerLog('listen to port', port)
showServerLog('http server listen to', port)
}

_close () {
Expand All @@ -275,16 +274,6 @@ export class WsClient extends Server {
public socket: WebSocket
private _listeners: Record<number, (response: CQResponse) => void> = {}

constructor (app: App) {
super(app)

this.socket = new WebSocket(app.options.server, {
headers: {
Authorization: `Bearer ${app.options.token}`,
},
})
}

send (data: any): Promise<CQResponse> {
data.echo = ++counter
return new Promise((resolve, reject) => {
Expand All @@ -297,6 +286,12 @@ export class WsClient extends Server {

_listen (): Promise<void> {
return new Promise((resolve, reject) => {
showServerLog('websocket client opening')
const headers: Record<string, string> = {}
const { token, server } = this.appList[0].options
if (token) headers.Authorization = `Bearer ${token}`
this.socket = new WebSocket(server, { headers })

this.socket.once('error', reject)

this.socket.once('open', () => {
Expand All @@ -310,11 +305,12 @@ export class WsClient extends Server {
let resolved = false
this.socket.on('message', (data) => {
data = data.toString()
showServerLog('receive', data)
let parsed: any
try {
parsed = JSON.parse(data)
} catch (error) {
return reject(data)
return reject(new Error(data))
}
if (!resolved) {
resolved = true
Expand All @@ -338,32 +334,36 @@ export class WsClient extends Server {

_close () {
this.socket.close()
showServerLog('ws client closed')
showServerLog('websocket client closed')
}
}

export type ServerType = 'http' | 'ws' // 'ws-reverse'

const serverTypes: Record<ServerType, [keyof AppOptions, Record<keyof any, Server>, new (app: App) => Server]> = {
http: ['port', {}, HttpServer],
ws: ['server', {}, WsClient],
}
export const serverMap: Record<ServerType, Record<keyof any, Server>> = { http: {}, ws: {} }

export function createServer (app: App) {
if (typeof app.options.type !== 'string') {
throw new Error(errors.UNSUPPORTED_SERVER_TYPE)
}
app.options.type = app.options.type.toLowerCase() as any
if (!serverTypes[app.options.type]) {
let key: keyof any, Server: new (app: App) => Server
if (app.options.type === 'http') {
key = 'port'
Server = HttpServer
} else if (app.options.type === 'ws') {
key = 'server'
Server = WsClient
} else {
throw new Error(errors.UNSUPPORTED_SERVER_TYPE)
}
const [key, serverMap, Server] = serverTypes[app.options.type]
const value = app.options[key] as any
const servers = serverMap[app.options.type]
const value = app.options[key]
if (!value) {
throw new Error(format(errors.MISSING_CONFIGURATION, key))
}
if (value in serverMap) {
return serverMap[value].bind(app)
if (value in servers) {
return servers[value].bind(app)
}
return serverMap[value] = new Server(app)
return servers[value] = new Server(app)
}
166 changes: 69 additions & 97 deletions packages/koishi-core/tests/ws-client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,37 @@
import { wsClient } from 'koishi-test-utils'
import { Meta } from 'koishi-core'
import { createWsServer, WsServer, BASE_SELF_ID } from 'koishi-test-utils'
import { App, Meta } from 'koishi-core'

const { createApp, createServer, postMeta, SERVER_PORT, emitter, nextTick } = wsClient
let server: WsServer
let app1: App, app2: App

const server2 = createServer(SERVER_PORT + 1, true)
const server = createServer()

const app1 = createApp()
const app2 = createApp({ selfId: 515 })

jest.setTimeout(1000)

beforeAll(() => {
return Promise.all([
app1.start(),
app2.start(),
])
beforeAll(async () => {
server = await createWsServer()
app1 = server.createBoundApp()
app2 = server.createBoundApp({ selfId: BASE_SELF_ID + 1 })
})

afterAll(() => {
server.close()
server2.close()

return Promise.all([
app1.stop(),
app2.stop(),
])
})
afterAll(() => server.close())

describe('WebSocket Server', () => {
const mocks: jest.Mock[] = []
for (let index = 0; index < 3; ++index) {
mocks.push(jest.fn())
}
const app1MessageCallback = jest.fn()
const app2MessageCallback = jest.fn()

app1.receiver.on('message', mocks[0])
app2.receiver.on('message', mocks[1])
beforeAll(() => {
app1.receiver.on('message', app1MessageCallback)
app2.receiver.on('message', app2MessageCallback)
})

test('authorization', async () => {
server.token = 'token'
await expect(app1.start()).rejects.toHaveProperty('message', 'authorization failed')
app1.options.token = 'nekot'
await expect(app1.start()).rejects.toHaveProperty('message', 'authorization failed')
app1.options.token = 'token'
await expect(app1.start()).resolves.toBeUndefined()
server.token = null
await app1.stop()
await expect(app1.start()).resolves.toBeUndefined()
})

const meta: Meta = {
postType: 'message',
Expand All @@ -45,31 +41,25 @@ describe('WebSocket Server', () => {
message: 'Hello',
}

test('authorization failed', async () => {
const app = createApp({ server: `ws://localhost:${SERVER_PORT + 1}` })
await expect(app.start()).rejects.toBe('authorization failed')
await app.stop()
})

test('app binding', async () => {
await postMeta({ ...meta, selfId: 514 })
expect(mocks[0]).toBeCalledTimes(1)
expect(mocks[1]).toBeCalledTimes(0)
await server.post({ ...meta, selfId: BASE_SELF_ID })
expect(app1MessageCallback).toBeCalledTimes(1)
expect(app2MessageCallback).toBeCalledTimes(0)

await postMeta({ ...meta, selfId: 515 })
expect(mocks[0]).toBeCalledTimes(1)
expect(mocks[1]).toBeCalledTimes(1)
await server.post({ ...meta, selfId: BASE_SELF_ID + 1 })
expect(app1MessageCallback).toBeCalledTimes(1)
expect(app2MessageCallback).toBeCalledTimes(1)

await postMeta({ ...meta, selfId: 516 })
expect(mocks[0]).toBeCalledTimes(1)
expect(mocks[1]).toBeCalledTimes(1)
await server.post({ ...meta, selfId: BASE_SELF_ID + 2 })
expect(app1MessageCallback).toBeCalledTimes(1)
expect(app2MessageCallback).toBeCalledTimes(1)
})

test('make polyfills', async () => {
await postMeta({ postType: 'message', groupId: 123, message: [{ type: 'text', data: { text: 'foo' } }] as any })
await postMeta({ postType: 'message', groupId: 123, message: '', anonymous: 'foo' as any, ['anonymousFlag' as any]: 'bar' })
await postMeta({ postType: 'request', userId: 123, requestType: 'friend', message: 'baz' })
await postMeta({ postType: 'event' as any, userId: 123, ['event' as any]: 'frient_add' })
await server.post({ postType: 'message', groupId: 123, message: [{ type: 'text', data: { text: 'foo' } }] as any })
await server.post({ postType: 'message', groupId: 123, message: '', anonymous: 'foo' as any, ['anonymousFlag' as any]: 'bar' })
await server.post({ postType: 'request', userId: 123, requestType: 'friend', message: 'baz' })
await server.post({ postType: 'event' as any, userId: 123, ['event' as any]: 'frient_add' })
})
})

Expand Down Expand Up @@ -112,81 +102,63 @@ describe('Quick Operations', () => {
test('message event', async () => {
mock.mockClear()
app1.receiver.once('message', meta => meta.$send('foo'))
emitter.once('send_group_msg_async', mock)
await postMeta(messageMeta)
await nextTick()
expect(mock).toBeCalledTimes(1)
expect(mock.mock.calls[0][0]).toMatchObject({ group_id: 20000, message: 'foo' })
await server.post(messageMeta)
await server.nextTick()
server.shouldHaveLastRequest('send_group_msg_async', { groupId: 20000, message: 'foo' })

mock.mockClear()
app1.groups.receiver.once('message', meta => meta.$ban())
emitter.once('set_group_ban_async', mock)
await postMeta(messageMeta)
await nextTick()
expect(mock).toBeCalledTimes(1)
expect(mock.mock.calls[0][0]).toMatchObject({ group_id: 20000, user_id: 10000 })
await server.post(messageMeta)
await server.nextTick()
server.shouldHaveLastRequest('set_group_ban_async', { groupId: 20000, userId: 10000 })

mock.mockClear()
app1.groups.receiver.once('message', meta => meta.$delete())
emitter.once('delete_msg_async', mock)
await postMeta(messageMeta)
await nextTick()
expect(mock).toBeCalledTimes(1)
expect(mock.mock.calls[0][0]).toMatchObject({ message_id: 99999 })
await server.post(messageMeta)
await server.nextTick()
server.shouldHaveLastRequest('delete_msg_async', { messageId: 99999 })

mock.mockClear()
app1.groups.receiver.once('message', meta => meta.$kick())
emitter.once('set_group_kick_async', mock)
await postMeta(messageMeta)
await nextTick()
expect(mock).toBeCalledTimes(1)
expect(mock.mock.calls[0][0]).toMatchObject({ group_id: 20000, user_id: 10000 })
await server.post(messageMeta)
await server.nextTick()
server.shouldHaveLastRequest('set_group_kick_async', { groupId: 20000, userId: 10000 })

mock.mockClear()
app1.groups.receiver.once('message', meta => meta.$ban())
emitter.once('set_group_anonymous_ban_async', mock)
await postMeta(anonymousMeta)
await nextTick()
expect(mock).toBeCalledTimes(1)
expect(mock.mock.calls[0][0]).toMatchObject({ group_id: 20000, flag: 'flag' })
await server.post(anonymousMeta)
await server.nextTick()
server.shouldHaveLastRequest('set_group_anonymous_ban_async', { groupId: 20000, flag: 'flag' })

mock.mockClear()
app1.groups.receiver.once('message', meta => meta.$kick())
await postMeta(anonymousMeta)
await expect(nextTick()).rejects.toBeTruthy()
await server.post(anonymousMeta)
// should have no response, just make coverage happy
})

test('request event', async () => {
mock.mockClear()
app1.receiver.once('request/friend', meta => meta.$approve('foo'))
emitter.once('set_friend_add_request_async', mock)
await postMeta(frientRequestMeta)
await nextTick()
expect(mock).toBeCalledTimes(1)
expect(mock.mock.calls[0][0]).toMatchObject({ flag: 'foo', remark: 'foo', approve: true })
await server.post(frientRequestMeta)
await server.nextTick()
server.shouldHaveLastRequest('set_friend_add_request_async', { flag: 'foo', remark: 'foo', approve: true })

mock.mockClear()
app1.receiver.once('request/friend', meta => meta.$reject())
emitter.once('set_friend_add_request_async', mock)
await postMeta(frientRequestMeta)
await nextTick()
expect(mock).toBeCalledTimes(1)
expect(mock.mock.calls[0][0]).toMatchObject({ flag: 'foo', approve: false })
await server.post(frientRequestMeta)
await server.nextTick()
server.shouldHaveLastRequest('set_friend_add_request_async', { flag: 'foo', approve: false })

mock.mockClear()
app1.receiver.once('request/group/add', meta => meta.$approve())
emitter.once('set_group_add_request_async', mock)
await postMeta(groupRequestMeta)
await nextTick()
expect(mock).toBeCalledTimes(1)
expect(mock.mock.calls[0][0]).toMatchObject({ flag: 'bar', approve: true })
await server.post(groupRequestMeta)
await server.nextTick()
server.shouldHaveLastRequest('set_group_add_request_async', { flag: 'bar', approve: true })

mock.mockClear()
app1.receiver.once('request/group/add', meta => meta.$reject('bar'))
emitter.once('set_group_add_request_async', mock)
await postMeta(groupRequestMeta)
await nextTick()
expect(mock).toBeCalledTimes(1)
expect(mock.mock.calls[0][0]).toMatchObject({ flag: 'bar', reason: 'bar', approve: false })
await server.post(groupRequestMeta)
await server.nextTick()
server.shouldHaveLastRequest('set_group_add_request_async', { flag: 'bar', reason: 'bar', approve: false })
})
})
6 changes: 1 addition & 5 deletions packages/test-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import * as wsClient from './ws-client'

export * from './database'
export * from './memory'
export * from './mocks'
export * from './utils'
export * from './http-server'

export { wsClient }
export * from './server'
6 changes: 2 additions & 4 deletions packages/test-utils/src/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BASE_SELF_ID } from './utils'
import { BASE_SELF_ID, RequestData } from './utils'
import { snakeCase, sleep } from 'koishi-utils'
import { AppOptions, App, Sender, Server, ContextType, ResponsePayload, MessageMeta, Meta } from 'koishi-core'

Expand All @@ -15,10 +15,8 @@ class MockedServer extends Server {
}
}

export type RequestInfo = readonly [string, Record<string, any>]

class MockedSender extends Sender {
requests: RequestInfo[] = []
requests: RequestData[] = []

constructor (app: App) {
super(app)
Expand Down
Loading

0 comments on commit e8fff6a

Please sign in to comment.