typescript(lunargame/api): overall better tests (except for the validate ones)
Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
parent
ffe0ad26ea
commit
48edeb1093
|
@ -20,6 +20,7 @@
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/explicit-function-return-type": 0,
|
"@typescript-eslint/explicit-function-return-type": 0,
|
||||||
"@typescript-eslint/no-object-literal-type-assertion": 0
|
"@typescript-eslint/no-object-literal-type-assertion": 0,
|
||||||
|
"@typescript-eslint/no-parameter-properties": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
typescript/lunargame/api/.gitignore
vendored
1
typescript/lunargame/api/.gitignore
vendored
|
@ -2,3 +2,4 @@ node_modules
|
||||||
.env
|
.env
|
||||||
test/db.sqlite
|
test/db.sqlite
|
||||||
db/db.sqlite
|
db/db.sqlite
|
||||||
|
coverage
|
|
@ -5,5 +5,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
|
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
|
||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||||
testEnvironment: 'node'
|
testEnvironment: 'node',
|
||||||
|
collectCoverage: true,
|
||||||
|
coverageReporters: ['json', 'html']
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import Joi from 'joi'
|
||||||
|
import { validate, validationField } from './validate'
|
||||||
|
import { Context } from 'koa'
|
||||||
|
import { fakeNext } from '../../../../test/utils/fakeNext'
|
||||||
|
|
||||||
|
describe('The validate middleware', () => {
|
||||||
|
const schema = Joi.object({
|
||||||
|
name: Joi.required()
|
||||||
|
})
|
||||||
|
|
||||||
|
const fields: validationField[] = ['body', 'params', 'query']
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
describe(`The request ${field} validator`, () => {
|
||||||
|
const middleware = validate(schema, field)
|
||||||
|
|
||||||
|
const getContext = (name?: number) => {
|
||||||
|
if (field === 'body') {
|
||||||
|
return {
|
||||||
|
request: {
|
||||||
|
body: {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as Context
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
[field]: {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
} as Context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('should throw an error if the validation fails', () => {
|
||||||
|
// arrange
|
||||||
|
const context = getContext()
|
||||||
|
|
||||||
|
// act
|
||||||
|
const check = () => {
|
||||||
|
middleware(context, fakeNext())
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(check).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should call next if the validation passed', () => {
|
||||||
|
// arrange
|
||||||
|
const context = getContext(7)
|
||||||
|
|
||||||
|
const next = jest.fn(fakeNext())
|
||||||
|
|
||||||
|
// act
|
||||||
|
middleware(context, next)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(next).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
|
@ -2,6 +2,11 @@ import { ObjectSchema } from 'joi'
|
||||||
import { Middleware } from 'koa'
|
import { Middleware } from 'koa'
|
||||||
import { HttpError } from '../../../modules/network/classes/HttpError'
|
import { HttpError } from '../../../modules/network/classes/HttpError'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The field wich the validate validator can use
|
||||||
|
*/
|
||||||
|
export type validationField = 'params' | 'body' | 'query'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middlware to validate a joi schema against a request
|
* Middlware to validate a joi schema against a request
|
||||||
*
|
*
|
||||||
|
@ -10,15 +15,17 @@ import { HttpError } from '../../../modules/network/classes/HttpError'
|
||||||
*
|
*
|
||||||
* @throws HttpError if the validation fails
|
* @throws HttpError if the validation fails
|
||||||
*/
|
*/
|
||||||
export const validate = (
|
export const validate = (schema: ObjectSchema, field: validationField): Middleware => (
|
||||||
schema: ObjectSchema,
|
context,
|
||||||
field: 'params' | 'body' | 'query'
|
next
|
||||||
): Middleware => async (context, next) => {
|
) => {
|
||||||
const result = schema.validate(
|
const result = schema.validate(field === 'body' ? context.request.body : context[field], {
|
||||||
field === 'body' ? context.request.body : context[field]
|
abortEarly: true
|
||||||
)
|
})
|
||||||
|
|
||||||
if (result.error) throw new HttpError(400, result.error.message)
|
if (result.error !== null) {
|
||||||
|
throw new HttpError(422, result.error.message)
|
||||||
|
}
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,59 @@
|
||||||
import { checkPassword } from './checkPassword'
|
import { checkPassword } from './checkPassword'
|
||||||
import { passwordEncryption } from '../types/passwordEncryption'
|
import { passwordEncryption } from '../types/passwordEncryption'
|
||||||
|
import { hash, genSalt } from 'bcryptjs'
|
||||||
|
|
||||||
describe('The checkPassword helper', () => {
|
describe('The checkPassword helper', () => {
|
||||||
const pass = 'this is a test password'
|
const pass = 'this is a test password'
|
||||||
|
|
||||||
test("should throw an error if the encryption method doesn't exist", () => {
|
test("should throw an error if the encryption method doesn't exist", async () => {
|
||||||
expect(() => {
|
// arrange
|
||||||
checkPassword(pass, pass, '12212' as passwordEncryption)
|
const check = checkPassword(pass, pass, '12212' as passwordEncryption)
|
||||||
}).toThrow()
|
|
||||||
|
// assert
|
||||||
|
await expect(check).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should return true if the password matches the hash and the encryption = plain', () => {
|
describe("The 'plain' encryption", () => {
|
||||||
expect(checkPassword(pass, pass, 'plain')).toBe(true)
|
test('should return true if the password is correct', async () => {
|
||||||
|
// act
|
||||||
|
const check = await checkPassword(pass, pass, 'plain')
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(check).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('shoud return false if the password is wrong and the encryption = plain', () => {
|
test('shoud return false if the password is wrong', async () => {
|
||||||
expect(checkPassword(pass, pass + 'something', 'plain')).toBe(false)
|
// act
|
||||||
|
const check = await checkPassword(pass, pass + 'something', 'plain')
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(check).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("The 'bcrypt' encryption", () => {
|
||||||
|
let passwordHash: string
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const salt = await genSalt(3)
|
||||||
|
|
||||||
|
passwordHash = await hash(pass, salt)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should return true if the password is correct', async () => {
|
||||||
|
// act
|
||||||
|
const check = await checkPassword(passwordHash, pass, 'bcrypt')
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(check).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shoud return false if the password is wrong', async () => {
|
||||||
|
// act
|
||||||
|
const check = await checkPassword(passwordHash, pass + 'something', 'bcrypt')
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(check).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { passwordEncryption } from '../types/passwordEncryption'
|
import { passwordEncryption } from '../types/passwordEncryption'
|
||||||
import { HttpError } from '../../network/classes/HttpError'
|
import { HttpError } from '../../network/classes/HttpError'
|
||||||
|
import { compare } from 'bcryptjs'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comparesa apssword with it's hash
|
* Comparesa apssword with it's hash
|
||||||
|
@ -8,13 +9,15 @@ import { HttpError } from '../../network/classes/HttpError'
|
||||||
* @param password The actual password
|
* @param password The actual password
|
||||||
* @param encryption The encription of the password
|
* @param encryption The encription of the password
|
||||||
*/
|
*/
|
||||||
export const checkPassword = (
|
export const checkPassword = async (
|
||||||
hash: string,
|
hash: string,
|
||||||
password: string,
|
password: string,
|
||||||
encryption: passwordEncryption = 'plain'
|
encryption: passwordEncryption = 'plain'
|
||||||
) => {
|
) => {
|
||||||
if (encryption === 'plain') {
|
if (encryption === 'plain') {
|
||||||
return hash === password
|
return hash === password
|
||||||
|
} else if (encryption === 'bcrypt') {
|
||||||
|
return await compare(password, hash)
|
||||||
} else {
|
} else {
|
||||||
throw new HttpError(400, `Encription ${encryption} doesn't exist`)
|
throw new HttpError(400, `Encription ${encryption} doesn't exist`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,25 @@ import { compare } from 'bcryptjs'
|
||||||
|
|
||||||
describe('The encryptPassword helper', () => {
|
describe('The encryptPassword helper', () => {
|
||||||
test("should return the same password if the method is 'plain'", async () => {
|
test("should return the same password if the method is 'plain'", async () => {
|
||||||
|
// arrange
|
||||||
const password = internet.password()
|
const password = internet.password()
|
||||||
|
|
||||||
|
// act
|
||||||
const hash = await encryptPassword(password, 'plain')
|
const hash = await encryptPassword(password, 'plain')
|
||||||
|
|
||||||
|
// assert
|
||||||
expect(hash).toBe(password)
|
expect(hash).toBe(password)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should return a mactching hash if the method is 'bcrypt'", async () => {
|
test("should return a mactching hash if the method is 'bcrypt'", async () => {
|
||||||
|
// arrange
|
||||||
const password = internet.password()
|
const password = internet.password()
|
||||||
|
|
||||||
// the amount of rounds is small because this is just a test
|
|
||||||
const hash = await encryptPassword(password, 'bcrypt', 3)
|
const hash = await encryptPassword(password, 'bcrypt', 3)
|
||||||
|
|
||||||
|
// act
|
||||||
const match = await compare(password, hash)
|
const match = await compare(password, hash)
|
||||||
|
|
||||||
|
// assert
|
||||||
expect(match).toBe(true)
|
expect(match).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,28 +1,35 @@
|
||||||
import { Context } from 'koa'
|
import { Context } from 'koa'
|
||||||
import { isUnauthorized } from './isUnauthorized'
|
import { requireAnonymous } from './requireAnonymous'
|
||||||
|
import { fakeNext } from '../../../../test/utils/fakeNext'
|
||||||
describe('The isUnauthorized middleware', () => {
|
|
||||||
const fakeNext = () => async () => {}
|
|
||||||
|
|
||||||
|
describe('The requireAnonymous middleware', () => {
|
||||||
test('should throw an error if the user is logged in', () => {
|
test('should throw an error if the user is logged in', () => {
|
||||||
|
// act
|
||||||
const fakeContext = ({
|
const fakeContext = ({
|
||||||
session: {
|
session: {
|
||||||
uid: 7
|
uid: 7
|
||||||
}
|
}
|
||||||
} as unknown) as Context
|
} as unknown) as Context
|
||||||
|
|
||||||
expect(() => isUnauthorized()(fakeContext, fakeNext())).toThrow()
|
// arrange
|
||||||
|
const runMiddleware = () => requireAnonymous()(fakeContext, fakeNext())
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(runMiddleware).toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should call next if the user isn't logged in", () => {
|
test("should call next if the user isn't logged in", () => {
|
||||||
|
// arrange
|
||||||
const fakeContext = {
|
const fakeContext = {
|
||||||
session: {}
|
session: {}
|
||||||
} as Context
|
} as Context
|
||||||
|
|
||||||
const next = jest.fn(fakeNext())
|
const next = jest.fn(fakeNext())
|
||||||
|
|
||||||
isUnauthorized()(fakeContext, next)
|
// act
|
||||||
|
requireAnonymous()(fakeContext, next)
|
||||||
|
|
||||||
|
// assert
|
||||||
expect(next).toBeCalled()
|
expect(next).toBeCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -4,7 +4,7 @@ import { HttpError } from '../../network/classes/HttpError'
|
||||||
/**
|
/**
|
||||||
* Middleware wich throws an error if the user is logged in
|
* Middleware wich throws an error if the user is logged in
|
||||||
*/
|
*/
|
||||||
export const isUnauthorized = (): Middleware => (context, next) => {
|
export const requireAnonymous = (): Middleware => (context, next) => {
|
||||||
if (context.session.uid === undefined) {
|
if (context.session.uid === undefined) {
|
||||||
return next()
|
return next()
|
||||||
} else {
|
} else {
|
|
@ -1,18 +1,23 @@
|
||||||
import { isAuthorized } from './isAuthorized'
|
import { requireAuthenticated } from './requireAuthenticated'
|
||||||
import { Context } from 'koa'
|
import { Context } from 'koa'
|
||||||
|
import { fakeNext } from '../../../../test/utils/fakeNext'
|
||||||
|
|
||||||
describe('The isAuthorized middleware', () => {
|
describe('The requireAuthenticated middleware', () => {
|
||||||
const fakeNext = () => async () => {}
|
|
||||||
|
|
||||||
test("should throw an error if the user isn't logged in", () => {
|
test("should throw an error if the user isn't logged in", () => {
|
||||||
|
// arrange
|
||||||
const fakeContext = {
|
const fakeContext = {
|
||||||
session: {}
|
session: {}
|
||||||
} as Context
|
} as Context
|
||||||
|
|
||||||
expect(() => isAuthorized()(fakeContext, fakeNext())).toThrow()
|
// arrange
|
||||||
|
const runMiddleware = () => requireAuthenticated()(fakeContext, fakeNext())
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(runMiddleware).toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should call next if the user is logged in', () => {
|
test('should call next if the user is logged in', () => {
|
||||||
|
// arrange
|
||||||
const fakeContext = ({
|
const fakeContext = ({
|
||||||
session: {
|
session: {
|
||||||
uid: Math.random()
|
uid: Math.random()
|
||||||
|
@ -21,8 +26,10 @@ describe('The isAuthorized middleware', () => {
|
||||||
|
|
||||||
const next = jest.fn(fakeNext())
|
const next = jest.fn(fakeNext())
|
||||||
|
|
||||||
isAuthorized()(fakeContext, next)
|
// act
|
||||||
|
requireAuthenticated()(fakeContext, next)
|
||||||
|
|
||||||
|
// assert
|
||||||
expect(next).toBeCalled()
|
expect(next).toBeCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -4,7 +4,7 @@ import { HttpError } from '../../network/classes/HttpError'
|
||||||
/**
|
/**
|
||||||
* Middlware wich throws an error if the user isn't logged in
|
* Middlware wich throws an error if the user isn't logged in
|
||||||
*/
|
*/
|
||||||
export const isAuthorized = (): Middleware => (context, next) => {
|
export const requireAuthenticated = (): Middleware => (context, next) => {
|
||||||
if (context.session.uid !== undefined) {
|
if (context.session.uid !== undefined) {
|
||||||
return next()
|
return next()
|
||||||
} else {
|
} else {
|
|
@ -3,93 +3,110 @@ import { app } from '../../../server'
|
||||||
import { loggedInAgent } from '../../../../test/utils/loggedInAgent'
|
import { loggedInAgent } from '../../../../test/utils/loggedInAgent'
|
||||||
import { mockAccounts } from '../../../../test/seeds/01_create-account'
|
import { mockAccounts } from '../../../../test/seeds/01_create-account'
|
||||||
import { random, internet } from 'faker'
|
import { random, internet } from 'faker'
|
||||||
import { LoginReponseBody } from '../types/LoginReponseBody'
|
|
||||||
import { defaultEncryptionMethod } from '../constants'
|
import { defaultEncryptionMethod } from '../constants'
|
||||||
|
|
||||||
describe('The /auth route', () => {
|
describe('The /auth route', () => {
|
||||||
// used to make requests
|
// used to make requests
|
||||||
let request = supertest(app.callback())
|
const request = supertest(app.callback())
|
||||||
|
|
||||||
describe(`The POST method on the /login subroute`, () => {
|
|
||||||
test('should throw an error if the password field is empty', async () => {
|
|
||||||
const response = await request.post('/auth/login').send({
|
|
||||||
name: mockAccounts[0].name
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(response.status).not.toBe(200)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should throw an error if the name field is empty', async () => {
|
|
||||||
const response = await request.post('/auth/login').send({
|
|
||||||
password: mockAccounts[0].password
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(response.status).not.toBe(200)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should throw an error if the password is wrong', async () => {
|
|
||||||
const response = await request.post('/auth/login').send({
|
|
||||||
name: mockAccounts[0].name,
|
|
||||||
password: mockAccounts[0].password + 'something'
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(response.status).not.toBe(200)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should work just fine when the password is correct', async () => {
|
|
||||||
for (const account of mockAccounts) {
|
|
||||||
const response = await request.post('/auth/login').send({
|
|
||||||
email: account.email,
|
|
||||||
password: account.password
|
|
||||||
})
|
|
||||||
|
|
||||||
// i'm making a separate constant for vsc to help me
|
|
||||||
const body: LoginReponseBody = response.body
|
|
||||||
|
|
||||||
expect(response.status).toBe(200)
|
|
||||||
expect(body.uid).not.toBe(undefined)
|
|
||||||
expect(body.encryption).toBe(account.passwordEncryption)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe(`The GET method on the / subroute`, () => {
|
describe(`The GET method on the / subroute`, () => {
|
||||||
test('should return undefined if the user was not logged in', async () => {
|
test('should return undefined if the user was not logged in', async () => {
|
||||||
|
// act
|
||||||
const res = await request.get('/auth')
|
const res = await request.get('/auth')
|
||||||
|
|
||||||
|
// assert
|
||||||
expect(res.body.uid).toBe(undefined)
|
expect(res.body.uid).toBe(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should return the uid form the session while logged in', async () => {
|
test('should return the uid form the session while logged in', async () => {
|
||||||
|
// arrange
|
||||||
const [agent, cookie] = await loggedInAgent(supertest.agent(app.callback()), {
|
const [agent, cookie] = await loggedInAgent(supertest.agent(app.callback()), {
|
||||||
email: mockAccounts[0].email,
|
email: mockAccounts[0].email,
|
||||||
password: mockAccounts[0].password
|
password: mockAccounts[0].password
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// act
|
||||||
const response = await agent.get('/auth').set('cookie', cookie)
|
const response = await agent.get('/auth').set('cookie', cookie)
|
||||||
|
|
||||||
|
// assert
|
||||||
expect(response.body.uid).not.toBe(undefined)
|
expect(response.body.uid).not.toBe(undefined)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe(`The POST method on the /login subroute`, () => {
|
||||||
|
test('should throw an error if the user is already logged in', async () => {
|
||||||
|
// arrange
|
||||||
|
const [agent, cookie] = await loggedInAgent(supertest.agent(app.callback()), {
|
||||||
|
email: mockAccounts[0].email,
|
||||||
|
password: mockAccounts[0].password
|
||||||
|
})
|
||||||
|
|
||||||
|
// act
|
||||||
|
const reponse = await agent.post('/auth/login').set('cookie', cookie)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(reponse.status).toBe(401)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw an error if the password is wrong', async () => {
|
||||||
|
// act
|
||||||
|
const response = await request.post('/auth/login').send({
|
||||||
|
email: mockAccounts[0].email,
|
||||||
|
password: mockAccounts[0].password + 'something'
|
||||||
|
})
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(response.status).toBe(422)
|
||||||
|
expect((response.body.message as string).startsWith('child')).toBe(false) // Not JOI
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should throw an error if the user doesn't exist", async () => {
|
||||||
|
// act
|
||||||
|
const reponse = await request.post('/auth/login').send({
|
||||||
|
email: 'idk' + mockAccounts[0].email,
|
||||||
|
password: mockAccounts[0].password
|
||||||
|
})
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(reponse.status).toBe(404)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work when the password is correct', async () => {
|
||||||
|
for (const account of mockAccounts) {
|
||||||
|
// act
|
||||||
|
const response = await request.post('/auth/login').send({
|
||||||
|
email: account.email,
|
||||||
|
password: account.password
|
||||||
|
})
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.body.uid).not.toBe(undefined)
|
||||||
|
expect(response.body.encryption).toBe(account.passwordEncryption)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('The POST method on the /signup subroute', () => {
|
describe('The POST method on the /signup subroute', () => {
|
||||||
test('should return the email name and the encrytion', async () => {
|
test('should work if all fields are correct', async () => {
|
||||||
const username = random.alphaNumeric(7)
|
// arrange
|
||||||
|
const name = internet.userName()
|
||||||
const password = random.alphaNumeric(5)
|
const password = random.alphaNumeric(5)
|
||||||
const email = internet.email()
|
const email = internet.email()
|
||||||
|
|
||||||
const response = await request.post('/auth/signup').send({
|
const user = {
|
||||||
name: username,
|
name,
|
||||||
email,
|
email,
|
||||||
password
|
password
|
||||||
})
|
}
|
||||||
|
|
||||||
// i'm making a separate constant for vsc to help me
|
// act
|
||||||
const body: LoginReponseBody = response.body
|
const response = await request.post('/auth/signup').send(user)
|
||||||
|
|
||||||
|
// assert
|
||||||
expect(response.status).toBe(200)
|
expect(response.status).toBe(200)
|
||||||
expect(body.uid).not.toBe(undefined)
|
expect(response.body.uid).not.toBe(undefined)
|
||||||
expect(body.encryption).toBe(defaultEncryptionMethod)
|
expect(response.body.encryption).toBe(defaultEncryptionMethod)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { encryptPassword } from '../helpers/encryptPassword'
|
||||||
import { createAccount } from '../queries/createAccount'
|
import { createAccount } from '../queries/createAccount'
|
||||||
import { defaultEncryptionMethod } from '../constants'
|
import { defaultEncryptionMethod } from '../constants'
|
||||||
import { LoginBodySchema } from '../schemas/LoginBody'
|
import { LoginBodySchema } from '../schemas/LoginBody'
|
||||||
import { isUnauthorized } from '../middleware/isUnauthorized'
|
import { requireAnonymous } from '../middleware/requireAnonymous'
|
||||||
|
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
|
@ -22,30 +22,28 @@ router.get('/', (context, next) => {
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/login',
|
'/login',
|
||||||
isUnauthorized(),
|
requireAnonymous(),
|
||||||
validate(LoginBodySchema, 'body'),
|
validate(LoginBodySchema, 'body'),
|
||||||
async (context, next) => {
|
async (context, next) => {
|
||||||
const { email, password } = context.request.body
|
const { email, password } = context.request.body
|
||||||
|
|
||||||
const passwordData = await getPasswordByEmail(email)
|
const passwordData = await getPasswordByEmail(email)
|
||||||
|
|
||||||
// in case the user doesnt exist
|
// in case the user doesnt exist
|
||||||
if (!passwordData) {
|
if (!passwordData) {
|
||||||
throw new HttpError(400)
|
throw new HttpError(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = checkPassword(
|
const match = await checkPassword(
|
||||||
passwordData.password,
|
passwordData.password,
|
||||||
password,
|
password,
|
||||||
passwordData.passwordEncryption
|
passwordData.passwordEncryption
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new HttpError(400, 'wrong password')
|
throw new HttpError(422, 'wrong password')
|
||||||
}
|
}
|
||||||
|
|
||||||
context.session.uid = passwordData.id
|
context.session.uid = passwordData.id
|
||||||
|
|
||||||
context.body = {
|
context.body = {
|
||||||
encryption: passwordData.passwordEncryption,
|
encryption: passwordData.passwordEncryption,
|
||||||
uid: passwordData.id
|
uid: passwordData.id
|
||||||
|
@ -57,7 +55,7 @@ router.post(
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/signup',
|
'/signup',
|
||||||
isUnauthorized(),
|
requireAnonymous(),
|
||||||
validate(SignupBodySchema, 'body'),
|
validate(SignupBodySchema, 'body'),
|
||||||
async (context, next) => {
|
async (context, next) => {
|
||||||
const { email, name, password } = context.request.body
|
const { email, name, password } = context.request.body
|
||||||
|
|
|
@ -11,6 +11,7 @@ export enum HttpStatus {
|
||||||
Conflict = 409,
|
Conflict = 409,
|
||||||
Gone = 410,
|
Gone = 410,
|
||||||
PayloadTooLarge = 413,
|
PayloadTooLarge = 413,
|
||||||
|
UnprocessableEntity = 422,
|
||||||
TooManyRequests = 429,
|
TooManyRequests = 429,
|
||||||
InternalServerError = 500
|
InternalServerError = 500
|
||||||
}
|
}
|
||||||
|
@ -24,6 +25,7 @@ export const HTTP_REASONS: Record<HttpStatus, string> = {
|
||||||
'409': 'Conflict',
|
'409': 'Conflict',
|
||||||
'410': 'Gone',
|
'410': 'Gone',
|
||||||
'413': 'Payload too large',
|
'413': 'Payload too large',
|
||||||
|
'422': 'Validation error',
|
||||||
'429': 'Too many requests',
|
'429': 'Too many requests',
|
||||||
'500': 'Internal server error'
|
'500': 'Internal server error'
|
||||||
}
|
}
|
||||||
|
@ -34,7 +36,7 @@ export class HttpError extends Error {
|
||||||
// for some reason instanceof stopped working at some point
|
// for some reason instanceof stopped working at some point
|
||||||
public [httpSymbol] = true
|
public [httpSymbol] = true
|
||||||
|
|
||||||
constructor(
|
public constructor(
|
||||||
public status: HttpStatus = HttpStatus.InternalServerError,
|
public status: HttpStatus = HttpStatus.InternalServerError,
|
||||||
public reason?: string
|
public reason?: string
|
||||||
) {
|
) {
|
||||||
|
|
4
typescript/lunargame/api/test/utils/fakeNext.ts
Normal file
4
typescript/lunargame/api/test/utils/fakeNext.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Factory for a quick mock of the next function required to test middlewares
|
||||||
|
*/
|
||||||
|
export const fakeNext = () => async () => {}
|
Loading…
Reference in a new issue