From 3749913d5182a9558d7827a314372232680709b4 Mon Sep 17 00:00:00 2001 From: Mark Tyers Date: Sun, 15 Sep 2019 12:47:54 +0100 Subject: [PATCH] completed unit tests for module --- .vscode/launch.json | 8 +++++ index.js | 63 +++++++++--------------------------- modules/user.js | 49 +++++++++++++++++++--------- unit tests/user.spec.js | 71 ++++++++++++++++++++++++++++++++++------- 4 files changed, 117 insertions(+), 74 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f221d6c..ff946a2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,6 +2,13 @@ { "version": "0.2.0", "configurations": [ + + { + "type": "node", + "request": "attach", + "name": "Attach by Process ID", + "processId": "${command:PickProcess}" + }, { "type": "node", "request": "launch", @@ -11,6 +18,7 @@ "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "disableOptimisticBPs": true, + "protocol": "inspector", "windows": { "program": "${workspaceFolder}/node_modules/jest/bin/jest", } diff --git a/index.js b/index.js index 645e106..c4962a9 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ 'use strict' /* MODULE IMPORTS */ +const bcrypt = require('bcrypt-promise') const Koa = require('koa') const Router = require('koa-router') const views = require('koa-views') @@ -15,13 +16,12 @@ const bodyParser = require('koa-bodyparser') const koaBody = require('koa-body')({multipart: true, uploadDir: '.'}) const session = require('koa-session') const sqlite = require('sqlite-async') -const bcrypt = require('bcrypt-promise') const fs = require('fs-extra') const mime = require('mime-types') //const jimp = require('jimp') /* IMPORT CUSTOM MODULES */ -const accounts = require('./modules/accounts') +const User = require('./modules/user') const app = new Koa() const router = new Router() @@ -35,6 +35,7 @@ app.use(views(`${__dirname}/views`, { extension: 'handlebars' }, {map: { handleb const defaultPort = 8080 const port = process.env.PORT || defaultPort +const dbName = 'website.db' const saltRounds = 10 /** @@ -71,24 +72,15 @@ router.get('/register', async ctx => await ctx.render('register')) */ router.post('/register', koaBody, async ctx => { try { + // extract the data from the request const body = ctx.request.body console.log(body) - // PROCESSING FILE const {path, type} = ctx.request.files.avatar - const fileExtension = mime.extension(type) - console.log(`path: ${path}`) - console.log(`type: ${type}`) - console.log(`fileExtension: ${fileExtension}`) - await fs.copy(path, 'public/avatars/avatar.png') - // ENCRYPTING PASSWORD AND BUILDING SQL - body.pass = await bcrypt.hash(body.pass, saltRounds) - const sql = `INSERT INTO users(user, pass) VALUES("${body.user}", "${body.pass}")` - console.log(sql) - // DATABASE COMMANDS - const db = await sqlite.open('./website.db') - await db.run(sql) - await db.close() - // REDIRECTING USER TO HOME PAGE + // call the functions in the module + const user = await new User(dbName) + await user.register(body.user, body.pass) + // await user.uploadPicture(path, type) + // redirect to the home page ctx.redirect(`/?msg=new user "${body.name}" added`) } catch(err) { await ctx.render('error', {message: err.message}) @@ -99,22 +91,14 @@ router.get('/login', async ctx => { const data = {} if(ctx.query.msg) data.msg = ctx.query.msg if(ctx.query.user) data.user = ctx.query.user - await ctx.render('login', data) + await ctx.render('login', data) }) router.post('/login', async ctx => { try { const body = ctx.request.body - const db = await sqlite.open('./website.db') - // DOES THE USERNAME EXIST? - const records = await db.get(`SELECT count(id) AS count FROM users WHERE user="${body.user}";`) - if(!records.count) return ctx.redirect('/login?msg=invalid%20username') - const record = await db.get(`SELECT pass FROM users WHERE user = "${body.user}";`) - await db.close() - // DOES THE PASSWORD MATCH? - const valid = await bcrypt.compare(body.pass, record.pass) - if(valid == false) return ctx.redirect(`/login?user=${body.user}&msg=invalid%20password`) - // WE HAVE A VALID USERNAME AND PASSWORD + const user = await new User(dbName) + await user.login(body.user, body.pass) ctx.session.authorised = true return ctx.redirect('/?msg=you are now logged in...') } catch(err) { @@ -122,27 +106,10 @@ router.post('/login', async ctx => { } }) -// router.post('/login', async ctx => { // 19 lines reduced to 10! -// const body = ctx.request.body -// try { -// await accounts.checkCredentials(body.user, body.pass) -// ctx.session.authorised = true -// return ctx.redirect('/?msg=you are now logged in...') -// } catch(err) { -// return ctx.redirect(`/login?user=${body.user}&msg=${err.message}`) -// } -// }) - router.get('/logout', async ctx => { - ctx.session.authorised = null; - ctx.redirect('/') + ctx.session.authorised = null + ctx.redirect('/?msg=you are now logged out') }) app.use(router.routes()) -module.exports = app.listen(port, async() => { - // MAKE SURE WE HAVE A DATABASE WITH THE CORRECT SCHEMA - const db = await sqlite.open('./website.db') - await db.run('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT, pass TEXT);') - await db.close() - console.log(`listening on port ${port}`) -}) \ No newline at end of file +module.exports = app.listen(port, async() => console.log(`listening on port ${port}`)) diff --git a/modules/user.js b/modules/user.js index 5d91927..fd1c7ec 100644 --- a/modules/user.js +++ b/modules/user.js @@ -1,6 +1,7 @@ 'use strict' +const bcrypt = require('bcrypt-promise') const fs = require('fs-extra') const mime = require('mime-types') const sqlite = require('sqlite-async') @@ -18,24 +19,42 @@ module.exports = class User { })() } - async register(username, password, filename, filetype) { - const fileExtension = mime.extension(filetype) - await fs.copy(path, `public/avatars/${username}.${fileExtension}`) - password = await bcrypt.hash(password, saltRounds) - const sql = `INSERT INTO users(user, pass) VALUES("${username}", "${password}")` - await this.db.run(sql) - return true + async register(user, pass) { + try { + if(user.length === 0) throw new Error('missing username') + if(pass.length === 0) throw new Error('missing password') + let sql = `SELECT COUNT(id) as records FROM users WHERE user="${user}";` + const data = await this.db.get(sql) + if(data.records !== 0) throw new Error(`username "${user}" already in use`) + pass = await bcrypt.hash(pass, saltRounds) + sql = `INSERT INTO users(user, pass) VALUES("${user}", "${pass}")` + await this.db.run(sql) + return true + } catch(err) { + throw err + } + } + + async uploadPicture(path, mimeType) { + const extension = mime.extension(mimeType) + console.log(`path: ${path}`) + console.log(`extension: ${extension}`) + //await fs.copy(path, `public/avatars/${username}.${fileExtension}`) } async login(username, password) { - let sql = `SELECT count(id) AS count FROM users WHERE user="${body.user}";` - const records = await this.db.get(sql) - if(!records.count) throw new Error(`username "${username}" not found`) - sql = `SELECT pass FROM users WHERE user = "${body.user}";` - const record = await db.get(sql) - const valid = await bcrypt.compare(body.pass, record.pass) - if(valid == false) throw new Error(`invalid password for account "${username}"`) - return true + try { + let sql = `SELECT count(id) AS count FROM users WHERE user="${username}";` + const records = await this.db.get(sql) + if(!records.count) throw new Error(`username "${username}" not found`) + sql = `SELECT pass FROM users WHERE user = "${username}";` + const record = await this.db.get(sql) + const valid = await bcrypt.compare(password, record.pass) + if(valid === false) throw new Error(`invalid password for account "${username}"`) + return true + } catch(err) { + throw err + } } } \ No newline at end of file diff --git a/unit tests/user.spec.js b/unit tests/user.spec.js index 776b786..d01fd30 100644 --- a/unit tests/user.spec.js +++ b/unit tests/user.spec.js @@ -1,32 +1,81 @@ 'use strict' -const Accounts = require('../modules/accounts.js') +const Accounts = require('../modules/user.js') describe('register()', () => { + test('register a valid account', async done => { expect.assertions(1) try { const account = await new Accounts() - // test goes here + const register = await account.register('doej', 'password') + expect(register).toBe(true) } catch(err) { done.fail(err) } finally { done() } }) + + test('register a duplicate username', async done => { + expect.assertions(1) + const account = await new Accounts() + await account.register('doej', 'password') + await expect( account.register('doej', 'password') ) + .rejects.toEqual( Error('username "doej" already in use') ) + done() + }) + + test('error if blank username', async done => { + expect.assertions(1) + const account = await new Accounts() + await expect( account.register('', 'password') ) + .rejects.toEqual( Error('missing username') ) + done() + }) + + test('error if blank password', async done => { + expect.assertions(1) + const account = await new Accounts() + await expect( account.register('doej', '') ) + .rejects.toEqual( Error('missing password') ) + done() + }) + +}) + +describe('uploadPicture()', () => { + // this would have to be done by mocking the file system + // perhaps using mock-fs? }) describe('login()', () => { - test('log in with valid credentials', done => { + test('log in with valid credentials', async done => { expect.assertions(1) - try { - const account = await new Accounts() - // test goes here - } catch(err) { - done.fail(err) - } finally { - done() - } + const account = await new Accounts() + await account.register('doej', 'password') + const valid = await account.login('doej', 'password') + expect(valid).toBe(true) + done() }) + + test('invalid username', async done => { + expect.assertions(1) + const account = await new Accounts() + await account.register('doej', 'password') + await expect( account.login('roej', 'password') ) + .rejects.toEqual( Error('username "roej" not found') ) + done() + }) + + test('invalid password', async done => { + expect.assertions(1) + const account = await new Accounts() + await account.register('doej', 'password') + await expect( account.login('doej', 'bad') ) + .rejects.toEqual( Error('invalid password for account "doej"') ) + done() + }) + })