Add first test for JSONRPC service

This commit is contained in:
Jerko Steiner 2019-09-07 09:38:43 +07:00
parent 0e69cf239a
commit 3739f27ebe
6 changed files with 115 additions and 27 deletions

View File

@ -1,6 +1,7 @@
import {ICredentials} from './ICredentials'
import {IUser} from './IUser'
import * as e from './entities'
import {keys} from 'ts-transformer-keys'
export interface IChangePasswordParams {
oldPassword: string
@ -13,8 +14,8 @@ export interface ICreateUserParams extends ICredentials {
}
export interface IUserService {
changePassword(params: IChangePasswordParams): Promise<void>
// validateCredentials(credentials: ICredentials): Promise<e.User | undefined>
findOne(id: number): Promise<IUser | undefined>
getProfile(): Promise<IUser>
findUserByEmail(email: string): Promise<IUser | undefined>
}
export const UserServiceMethods = keys<IUserService>()

View File

@ -10,6 +10,7 @@ export const constantId = (val: string) => () => val
export function createRemoteClient<T>(
url: string,
methods: Array<FunctionPropertyNames<T>>,
headers: Record<string, string> = {},
getNextRequestId: TRequestIdGenerator<string | number> = constantId('c'),
idempotentMethodRegex = IDEMPOTENT_METHOD_REGEX,
) {
@ -26,6 +27,7 @@ export function createRemoteClient<T>(
const response = await axios({
method: reqMethod,
url,
headers,
[payloadKey]: {
id,
jsonrpc: '2.0',

View File

@ -13,6 +13,8 @@ import * as routes from '../routes'
import { TransactionalRouter } from '../router'
import { IRoutes, IContext } from '@rondo.dev/common'
import { Express } from 'express-serve-static-core'
import { jsonrpc, bulkjsonrpc } from '@rondo.dev/jsonrpc'
import * as rpc from '../rpc'
export type ServerConfigurator<
T extends IServerConfig = IServerConfig
@ -31,6 +33,21 @@ export const configureServer: ServerConfigurator = (config, database) => {
userPermissions: new User.UserPermissions(database),
}
const rpcServices = {
userService: new rpc.UserService(database),
teamService: new rpc.TeamService(database, services.userPermissions),
}
const getContext = (req: Express.Request): IContext => ({user: req.user})
const rpcMiddleware = jsonrpc(
req => getContext(req),
logger,
// (details, invoke) => database
// .transactionManager
// .doInNewTransaction(() => invoke()),
)
const authenticator = new Middleware.Authenticator(services.authService)
const transactionManager = database.transactionManager
@ -90,6 +107,10 @@ export const configureServer: ServerConfigurator = (config, database) => {
],
error: new Middleware.ErrorApiHandler(logger).handle,
},
rpc: {
path: '/rpc',
handle: [bulkjsonrpc(rpcMiddleware, rpcServices)],
},
},
}
}

View File

@ -0,0 +1,48 @@
import {test} from '../test'
import {user} from '@rondo.dev/common'
describe('user', () => {
test.withDatabase()
let cookie!: string
let token!: string
let headers: Record<string, string> = {}
beforeEach(async () => {
await test.registerAccount()
const session = await test.login()
cookie = session.cookie
token = session.token
headers = {cookie, 'x-csrf-token': token}
})
const createService = () => {
return test.rpc<user.IUserService>(
'/rpc/userService',
user.UserServiceMethods,
headers,
)
}
describe('findOne', () => {
it('fetches current user\'s profile', async () => {
const profile = await createService().getProfile()
expect(profile.id).toEqual(jasmine.any(Number))
})
})
describe('findUserByEmail', () => {
it('returns undefined when user no found', async () => {
const profile = await createService().findUserByEmail(
'totallynonexisting@email.com')
expect(profile).toBe(undefined)
})
it('returns user profile when found', async () => {
const profile = await createService().findUserByEmail(
test.username)
expect(profile).not.toBe(undefined)
})
})
})

View File

@ -13,33 +13,13 @@ const MIN_PASSWORD_LENGTH = 10
export class UserService implements RPC<u.IUserService> {
constructor(protected readonly db: IDatabase) {}
async changePassword(context: IContext, params: u.IChangePasswordParams) {
async getProfile(context: IContext) {
const userId = context.user!.id
const {oldPassword, newPassword} = params
const userRepository = this.db.getRepository(User)
const user = await userRepository
.createQueryBuilder('user')
.select('user')
.addSelect('user.password')
.whereInIds([ userId ])
.getOne()
const isValid = await compare(oldPassword, user ? user.password! : '')
if (!(user && isValid)) {
throw createError(400, 'Passwords do not match')
}
const password = await this._hash(newPassword)
await userRepository
.update(userId, { password })
}
async findOne(context: IContext, id: number) {
const user = await this.db.getRepository(User).findOne(id, {
// current user should always exist in the database
const user = (await this.db.getRepository(User).findOne(userId, {
relations: ['emails'],
})
if (!user) {
return undefined
}
}))!
return {
id: user.id,

View File

@ -10,6 +10,9 @@ import {RequestTester} from './RequestTester'
import {Role} from '../entities/Role'
import {CORRELATION_ID} from '../middleware'
import shortid from 'shortid'
import { AddressInfo } from 'net'
import { createRemoteClient, FunctionPropertyNames, TAsyncified } from '@rondo.dev/jsonrpc'
import {Server} from 'http'
export class TestUtils<T extends IRoutes> {
readonly username = `test${process.env.JEST_WORKER_ID}@user.com`
@ -160,6 +163,39 @@ export class TestUtils<T extends IRoutes> {
`${this.bootstrap.getConfig().app.baseUrl.path!}${baseUrl}`)
}
/**
* Starts the server, invokes a rpc method, and closes the server after
* invocation.
*/
rpc = <S>(
serviceUrl: string,
methods: Array<FunctionPropertyNames<S>>,
headers: Record<string, string>,
) => {
const {app} = this
const url = `${this.bootstrap.getConfig().app.baseUrl.path!}${serviceUrl}`
const service = methods.reduce((obj, method) => {
obj[method] = async function makeRequest(...args: any[]) {
let server!: Server
await new Promise(resolve => {
server = app.listen(0, '127.0.0.1', resolve)
})
const addr = server.address() as AddressInfo
const fullUrl = `http://${addr.address}:${addr.port}${url}`
const remoteService = createRemoteClient<S>(fullUrl, methods, headers)
try {
return await remoteService[method](...args as any)
} finally {
await new Promise(resolve => server.close(resolve))
}
}
return obj
}, {} as any)
return service as TAsyncified<S>
}
private getCookies(setCookiesString: string[]): string {
return setCookiesString.map(c => c.split('; ')[0]).join('; ')
}