Add first test for JSONRPC service
This commit is contained in:
parent
0e69cf239a
commit
3739f27ebe
@ -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>()
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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)],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
48
packages/server/src/rpc/UserService.test.ts
Normal file
48
packages/server/src/rpc/UserService.test.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@ -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,
|
||||
|
||||
@ -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('; ')
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user