diff --git a/exercises/06_code_quality/README.md b/exercises/06_code_quality/README.md new file mode 100644 index 00000000..19df846d --- /dev/null +++ b/exercises/06_code_quality/README.md @@ -0,0 +1,8 @@ +# Assignment Template + +This repository contains the base files for the assignment. To make use of this carry out the following steps: + +1. Fork this repository but change its name (replace `xxx` with your university username): + 1. If this is your original assignment, `xxx-coursework`. + 2. If this is your resit assignment code, `xxx-resit`. +2. Replace the contents of this file with the details of the topic you have been assigned. diff --git a/exercises/06_code_quality/archive/auth.specx.js b/exercises/06_code_quality/archive/auth.specx.js new file mode 100644 index 00000000..6dfca2fd --- /dev/null +++ b/exercises/06_code_quality/archive/auth.specx.js @@ -0,0 +1,60 @@ + +'use strict' + +const puppeteer = require('puppeteer') + +let page +let browser + +const baseURL = 'http://localhost:8080' + +beforeAll( async() => { + const width = 800 + const height = 600 + browser = await puppeteer.launch({ + headless: true, + slowMo: 40, + args: [`--window-size=${width},${height}`, '--disable-http2'] + }) + page = await browser.newPage() + await page.setViewport({ width, height }) +}) + +afterAll( async() => await browser.close()) + +describe('checking home screen is protected', () => { + test('home screen redirects to login if not logged in', async done => { + await page.waitFor(1000) + await page.goto('http://localhost:8080/logout') + await page.waitFor(1000) + //await page.goto('http://localhost:8080/', { waitUntil: 'domcontentloaded' }) + const title = await page.title() + expect(title).toBe('Log In') + done() + }) +}) + +describe('registering an account', () => { + test('home screen redirects to login if not logged in', async done => { + try { + await page.waitFor(1000) + await page.goto(`${baseURL}/register`, { waitUntil: 'domcontentloaded' }) + await page.type('input[name=user]', 'jbloggs') + await page.type('input[name=pass]', 'p455w0rd') + const input = await page.$('input[name=avatar]') + await input.uploadFile('screenshots/person.png') + await page.screenshot({ path: 'screenshots/completedRegForm.png' }) + await page.click('input[type=submit]') + await page.waitFor(1000) + await page.screenshot({ path: 'screenshots/accountCreated.png' }) + const title = await page.title() + expect(title).toBe('Log In') + const text = await page.evaluate(() => document.body.textContent) + expect(text).toContain('you need to log in') + } catch(err) { + //done.fail(new Error(err.message)) + } finally { + done() + } + }) +}) diff --git a/exercises/06_code_quality/archive/countries.json b/exercises/06_code_quality/archive/countries.json new file mode 100644 index 00000000..fb6f4491 --- /dev/null +++ b/exercises/06_code_quality/archive/countries.json @@ -0,0 +1,242 @@ +[ + {"name":"Israel","dial_code":"+972","code":"IL"}, + {"name":"Afghanistan","dial_code":"+93","code":"AF"}, + {"name":"Albania","dial_code":"+355","code":"AL"}, + {"name":"Algeria","dial_code":"+213","code":"DZ"}, + {"name":"AmericanSamoa","dial_code":"+1 684","code":"AS"}, + {"name":"Andorra","dial_code":"+376","code":"AD"}, + {"name":"Angola","dial_code":"+244","code":"AO"}, + {"name":"Anguilla","dial_code":"+1 264","code":"AI"}, + {"name":"Antigua and Barbuda","dial_code":"+1268","code":"AG"}, + {"name":"Argentina","dial_code":"+54","code":"AR"}, + {"name":"Armenia","dial_code":"+374","code":"AM"}, + {"name":"Aruba","dial_code":"+297","code":"AW"}, + {"name":"Australia","dial_code":"+61","code":"AU"}, + {"name":"Austria","dial_code":"+43","code":"AT"}, + {"name":"Azerbaijan","dial_code":"+994","code":"AZ"}, + {"name":"Bahamas","dial_code":"+1 242","code":"BS"}, + {"name":"Bahrain","dial_code":"+973","code":"BH"}, + {"name":"Bangladesh","dial_code":"+880","code":"BD"}, + {"name":"Barbados","dial_code":"+1 246","code":"BB"}, + {"name":"Belarus","dial_code":"+375","code":"BY"}, + {"name":"Belgium","dial_code":"+32","code":"BE"}, + {"name":"Belize","dial_code":"+501","code":"BZ"}, + {"name":"Benin","dial_code":"+229","code":"BJ"}, + {"name":"Bermuda","dial_code":"+1 441","code":"BM"}, + {"name":"Bhutan","dial_code":"+975","code":"BT"}, + {"name":"Bosnia and Herzegovina","dial_code":"+387","code":"BA"}, + {"name":"Botswana","dial_code":"+267","code":"BW"}, + {"name":"Brazil","dial_code":"+55","code":"BR"}, + {"name":"British Indian Ocean Territory","dial_code":"+246","code":"IO"},{"name":"Bulgaria","dial_code":"+359","code":"BG"}, + {"name":"Burkina Faso","dial_code":"+226","code":"BF"}, + {"name":"Burundi","dial_code":"+257","code":"BI"}, + {"name":"Cambodia","dial_code":"+855","code":"KH"}, + {"name":"Cameroon","dial_code":"+237","code":"CM"}, + {"name":"Canada","dial_code":"+1","code":"CA"}, + {"name":"Cape Verde","dial_code":"+238","code":"CV"}, + {"name":"Cayman Islands","dial_code":"+ 345","code":"KY"}, + {"name":"Central African Republic","dial_code":"+236","code":"CF"}, + {"name":"Chad","dial_code":"+235","code":"TD"}, + {"name":"Chile","dial_code":"+56","code":"CL"}, + {"name":"China","dial_code":"+86","code":"CN"}, + {"name":"Christmas Island","dial_code":"+61","code":"CX"}, + {"name":"Colombia","dial_code":"+57","code":"CO"}, + {"name":"Comoros","dial_code":"+269","code":"KM"}, + {"name":"Congo","dial_code":"+242","code":"CG"}, + {"name":"Cook Islands","dial_code":"+682","code":"CK"}, + {"name":"Costa Rica","dial_code":"+506","code":"CR"}, + {"name":"Croatia","dial_code":"+385","code":"HR"}, + {"name":"Cuba","dial_code":"+53","code":"CU"}, + {"name":"Cyprus","dial_code":"+537","code":"CY"}, + {"name":"Czech Republic","dial_code":"+420","code":"CZ"}, + {"name":"Denmark","dial_code":"+45","code":"DK"}, + {"name":"Djibouti","dial_code":"+253","code":"DJ"}, + {"name":"Dominica","dial_code":"+1 767","code":"DM"}, + {"name":"Dominican Republic","dial_code":"+1 849","code":"DO"}, + {"name":"Ecuador","dial_code":"+593","code":"EC"}, + {"name":"Egypt","dial_code":"+20","code":"EG"}, + {"name":"El Salvador","dial_code":"+503","code":"SV"}, + {"name":"Equatorial Guinea","dial_code":"+240","code":"GQ"}, + {"name":"Eritrea","dial_code":"+291","code":"ER"}, + {"name":"Estonia","dial_code":"+372","code":"EE"}, + {"name":"Ethiopia","dial_code":"+251","code":"ET"}, + {"name":"Faroe Islands","dial_code":"+298","code":"FO"}, + {"name":"Fiji","dial_code":"+679","code":"FJ"}, + {"name":"Finland","dial_code":"+358","code":"FI"}, + {"name":"France","dial_code":"+33","code":"FR"}, + {"name":"French Guiana","dial_code":"+594","code":"GF"}, + {"name":"French Polynesia","dial_code":"+689","code":"PF"}, + {"name":"Gabon","dial_code":"+241","code":"GA"}, + {"name":"Gambia","dial_code":"+220","code":"GM"}, + {"name":"Georgia","dial_code":"+995","code":"GE"}, + {"name":"Germany","dial_code":"+49","code":"DE"}, + {"name":"Ghana","dial_code":"+233","code":"GH"}, + {"name":"Gibraltar","dial_code":"+350","code":"GI"}, + {"name":"Greece","dial_code":"+30","code":"GR"}, + {"name":"Greenland","dial_code":"+299","code":"GL"}, + {"name":"Grenada","dial_code":"+1 473","code":"GD"}, + {"name":"Guadeloupe","dial_code":"+590","code":"GP"}, + {"name":"Guam","dial_code":"+1 671","code":"GU"}, + {"name":"Guatemala","dial_code":"+502","code":"GT"}, + {"name":"Guinea","dial_code":"+224","code":"GN"}, + {"name":"Guinea-Bissau","dial_code":"+245","code":"GW"}, + {"name":"Guyana","dial_code":"+595","code":"GY"}, + {"name":"Haiti","dial_code":"+509","code":"HT"}, + {"name":"Honduras","dial_code":"+504","code":"HN"}, + {"name":"Hungary","dial_code":"+36","code":"HU"}, + {"name":"Iceland","dial_code":"+354","code":"IS"}, + {"name":"India","dial_code":"+91","code":"IN"}, + {"name":"Indonesia","dial_code":"+62","code":"ID"}, + {"name":"Iraq","dial_code":"+964","code":"IQ"}, + {"name":"Ireland","dial_code":"+353","code":"IE"}, + {"name":"Israel","dial_code":"+972","code":"IL"}, + {"name":"Italy","dial_code":"+39","code":"IT"}, + {"name":"Jamaica","dial_code":"+1 876","code":"JM"}, + {"name":"Japan","dial_code":"+81","code":"JP"}, + {"name":"Jordan","dial_code":"+962","code":"JO"}, + {"name":"Kazakhstan","dial_code":"+7 7","code":"KZ"}, + {"name":"Kenya","dial_code":"+254","code":"KE"}, + {"name":"Kiribati","dial_code":"+686","code":"KI"}, + {"name":"Kuwait","dial_code":"+965","code":"KW"}, + {"name":"Kyrgyzstan","dial_code":"+996","code":"KG"}, + {"name":"Latvia","dial_code":"+371","code":"LV"}, + {"name":"Lebanon","dial_code":"+961","code":"LB"}, + {"name":"Lesotho","dial_code":"+266","code":"LS"}, + {"name":"Liberia","dial_code":"+231","code":"LR"}, + {"name":"Liechtenstein","dial_code":"+423","code":"LI"}, + {"name":"Lithuania","dial_code":"+370","code":"LT"}, + {"name":"Luxembourg","dial_code":"+352","code":"LU"}, + {"name":"Madagascar","dial_code":"+261","code":"MG"}, + {"name":"Malawi","dial_code":"+265","code":"MW"}, + {"name":"Malaysia","dial_code":"+60","code":"MY"}, + {"name":"Maldives","dial_code":"+960","code":"MV"}, + {"name":"Mali","dial_code":"+223","code":"ML"}, + {"name":"Malta","dial_code":"+356","code":"MT"}, + {"name":"Marshall Islands","dial_code":"+692","code":"MH"}, + {"name":"Martinique","dial_code":"+596","code":"MQ"}, + {"name":"Mauritania","dial_code":"+222","code":"MR"}, + {"name":"Mauritius","dial_code":"+230","code":"MU"}, + {"name":"Mayotte","dial_code":"+262","code":"YT"}, + {"name":"Mexico","dial_code":"+52","code":"MX"}, + {"name":"Monaco","dial_code":"+377","code":"MC"}, + {"name":"Mongolia","dial_code":"+976","code":"MN"}, + {"name":"Montenegro","dial_code":"+382","code":"ME"}, + {"name":"Montserrat","dial_code":"+1664","code":"MS"}, + {"name":"Morocco","dial_code":"+212","code":"MA"}, + {"name":"Myanmar","dial_code":"+95","code":"MM"}, + {"name":"Namibia","dial_code":"+264","code":"NA"}, + {"name":"Nauru","dial_code":"+674","code":"NR"}, + {"name":"Nepal","dial_code":"+977","code":"NP"}, + {"name":"Netherlands","dial_code":"+31","code":"NL"}, + {"name":"Netherlands Antilles","dial_code":"+599","code":"AN"}, + {"name":"New Caledonia","dial_code":"+687","code":"NC"}, + {"name":"New Zealand","dial_code":"+64","code":"NZ"}, + {"name":"Nicaragua","dial_code":"+505","code":"NI"}, + {"name":"Niger","dial_code":"+227","code":"NE"}, + {"name":"Nigeria","dial_code":"+234","code":"NG"}, + {"name":"Niue","dial_code":"+683","code":"NU"}, + {"name":"Norfolk Island","dial_code":"+672","code":"NF"}, + {"name":"Northern Mariana Islands","dial_code":"+1 670","code":"MP"}, + {"name":"Norway","dial_code":"+47","code":"NO"}, + {"name":"Oman","dial_code":"+968","code":"OM"}, + {"name":"Pakistan","dial_code":"+92","code":"PK"}, + {"name":"Palau","dial_code":"+680","code":"PW"}, + {"name":"Panama","dial_code":"+507","code":"PA"}, + {"name":"Papua New Guinea","dial_code":"+675","code":"PG"}, + {"name":"Paraguay","dial_code":"+595","code":"PY"}, + {"name":"Peru","dial_code":"+51","code":"PE"}, + {"name":"Philippines","dial_code":"+63","code":"PH"}, + {"name":"Poland","dial_code":"+48","code":"PL"}, + {"name":"Portugal","dial_code":"+351","code":"PT"}, + {"name":"Puerto Rico","dial_code":"+1 939","code":"PR"}, + {"name":"Qatar","dial_code":"+974","code":"QA"}, + {"name":"Romania","dial_code":"+40","code":"RO"}, + {"name":"Rwanda","dial_code":"+250","code":"RW"}, + {"name":"Samoa","dial_code":"+685","code":"WS"}, + {"name":"San Marino","dial_code":"+378","code":"SM"}, + {"name":"Saudi Arabia","dial_code":"+966","code":"SA"}, + {"name":"Senegal","dial_code":"+221","code":"SN"}, + {"name":"Serbia","dial_code":"+381","code":"RS"}, + {"name":"Seychelles","dial_code":"+248","code":"SC"}, + {"name":"Sierra Leone","dial_code":"+232","code":"SL"}, + {"name":"Singapore","dial_code":"+65","code":"SG"}, + {"name":"Slovakia","dial_code":"+421","code":"SK"}, + {"name":"Slovenia","dial_code":"+386","code":"SI"}, + {"name":"Solomon Islands","dial_code":"+677","code":"SB"}, + {"name":"South Africa","dial_code":"+27","code":"ZA"}, + {"name":"South Georgia and the South Sandwich Islands","dial_code":"+500","code":"GS"}, + {"name":"Spain","dial_code":"+34","code":"ES"}, + {"name":"Sri Lanka","dial_code":"+94","code":"LK"}, + {"name":"Sudan","dial_code":"+249","code":"SD"}, + {"name":"Suriname","dial_code":"+597","code":"SR"}, + {"name":"Swaziland","dial_code":"+268","code":"SZ"}, + {"name":"Sweden","dial_code":"+46","code":"SE"}, + {"name":"Switzerland","dial_code":"+41","code":"CH"}, + {"name":"Tajikistan","dial_code":"+992","code":"TJ"}, + {"name":"Thailand","dial_code":"+66","code":"TH"}, + {"name":"Togo","dial_code":"+228","code":"TG"}, + {"name":"Tokelau","dial_code":"+690","code":"TK"}, + {"name":"Tonga","dial_code":"+676","code":"TO"}, + {"name":"Trinidad and Tobago","dial_code":"+1 868","code":"TT"}, + {"name":"Tunisia","dial_code":"+216","code":"TN"}, + {"name":"Turkey","dial_code":"+90","code":"TR"}, + {"name":"Turkmenistan","dial_code":"+993","code":"TM"}, + {"name":"Turks and Caicos Islands","dial_code":"+1 649","code":"TC"}, + {"name":"Tuvalu","dial_code":"+688","code":"TV"}, + {"name":"Uganda","dial_code":"+256","code":"UG"}, + {"name":"Ukraine","dial_code":"+380","code":"UA"}, + {"name":"United Arab Emirates","dial_code":"+971","code":"AE"}, + {"name":"United Kingdom","dial_code":"+44","code":"GB"}, + {"name":"United States","dial_code":"+1","code":"US"}, + {"name":"Uruguay","dial_code":"+598","code":"UY"}, + {"name":"Uzbekistan","dial_code":"+998","code":"UZ"}, + {"name":"Vanuatu","dial_code":"+678","code":"VU"}, + {"name":"Wallis and Futuna","dial_code":"+681","code":"WF"}, + {"name":"Yemen","dial_code":"+967","code":"YE"}, + {"name":"Zambia","dial_code":"+260","code":"ZM"}, + {"name":"Zimbabwe","dial_code":"+263","code":"ZW"}, + {"name":"Åland Islands","dial_code":"","code":"AX"}, + {"name":"Antarctica","dial_code":null,"code":"AQ"}, + {"name":"Bolivia, Plurinational State of","dial_code":"+591","code":"BO"}, + {"name":"Brunei Darussalam","dial_code":"+673","code":"BN"}, + {"name":"Cocos (Keeling) Islands","dial_code":"+61","code":"CC"}, + {"name":"Congo, The Democratic Republic of the","dial_code":"+243","code":"CD"}, + {"name":"Cote d'Ivoire","dial_code":"+225","code":"CI"}, + {"name":"Falkland Islands (Malvinas)","dial_code":"+500","code":"FK"}, + {"name":"Guernsey","dial_code":"+44","code":"GG"}, + {"name":"Holy See (Vatican City State)","dial_code":"+379","code":"VA"}, + {"name":"Hong Kong","dial_code":"+852","code":"HK"}, + {"name":"Iran, Islamic Republic of","dial_code":"+98","code":"IR"}, + {"name":"Isle of Man","dial_code":"+44","code":"IM"}, + {"name":"Jersey","dial_code":"+44","code":"JE"}, + {"name":"Korea, Democratic People's Republic of","dial_code":"+850","code":"KP"}, + {"name":"Korea, Republic of","dial_code":"+82","code":"KR"}, + {"name":"Lao People's Democratic Republic","dial_code":"+856","code":"LA"}, + {"name":"Libyan Arab Jamahiriya","dial_code":"+218","code":"LY"}, + {"name":"Macao","dial_code":"+853","code":"MO"}, + {"name":"Macedonia, The Former Yugoslav Republic of","dial_code":"+389","code":"MK"}, + {"name":"Micronesia, Federated States of","dial_code":"+691","code":"FM"}, + {"name":"Moldova, Republic of","dial_code":"+373","code":"MD"}, + {"name":"Mozambique","dial_code":"+258","code":"MZ"}, + {"name":"Palestinian Territory, Occupied","dial_code":"+970","code":"PS"},{"name":"Pitcairn","dial_code":"+872","code":"PN"}, + {"name":"Réunion","dial_code":"+262","code":"RE"}, + {"name":"Russia","dial_code":"+7","code":"RU"}, + {"name":"Saint Barthélemy","dial_code":"+590","code":"BL"}, + {"name":"Saint Helena, Ascension and Tristan Da Cunha","dial_code":"+290","code":"SH"}, + {"name":"Saint Kitts and Nevis","dial_code":"+1 869","code":"KN"}, + {"name":"Saint Lucia","dial_code":"+1 758","code":"LC"}, + {"name":"Saint Martin","dial_code":"+590","code":"MF"}, + {"name":"Saint Pierre and Miquelon","dial_code":"+508","code":"PM"}, + {"name":"Saint Vincent and the Grenadines","dial_code":"+1 784","code":"VC"}, + {"name":"Sao Tome and Principe","dial_code":"+239","code":"ST"}, + {"name":"Somalia","dial_code":"+252","code":"SO"}, + {"name":"Svalbard and Jan Mayen","dial_code":"+47","code":"SJ"}, + {"name":"Syrian Arab Republic","dial_code":"+963","code":"SY"}, + {"name":"Taiwan, Province of China","dial_code":"+886","code":"TW"}, + {"name":"Tanzania, United Republic of","dial_code":"+255","code":"TZ"}, + {"name":"Timor-Leste","dial_code":"+670","code":"TL"}, + {"name":"Venezuela, Bolivarian Republic of","dial_code":"+58","code":"VE"}, + {"name":"Viet Nam","dial_code":"+84","code":"VN"}, + {"name":"Virgin Islands, British","dial_code":"+1 284","code":"VG"}, + {"name":"Virgin Islands, U.S.","dial_code":"+1 340","code":"VI"} +] \ No newline at end of file diff --git a/exercises/06_code_quality/archive/index.specx.js b/exercises/06_code_quality/archive/index.specx.js new file mode 100644 index 00000000..93f7ad49 --- /dev/null +++ b/exercises/06_code_quality/archive/index.specx.js @@ -0,0 +1,33 @@ + +'use strict' + +const request = require('supertest') +const status = require('http-status-codes') + +const server = require('../../index.js') + +beforeAll( async() => console.log('Jest starting!')) + +describe('GET /', () => { + // TODO +}) + +describe('GET /register', () => { + // TODO +}) + +describe('POST /register', () => { + // TODO +}) + +describe('GET /login', () => { + // TODO +}) + +describe('POST /login', () => { + // TODO +}) + +describe('GET /logout', () => { + // TODO +}) diff --git a/exercises/06_code_quality/archive/test.sh b/exercises/06_code_quality/archive/test.sh new file mode 100755 index 00000000..f6c4ea02 --- /dev/null +++ b/exercises/06_code_quality/archive/test.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +node index.js& +node_modules/.bin/jest --detectOpenHandles tests/acceptance/ +kill %1 diff --git a/exercises/06_code_quality/archive/testdata/boy-2.png b/exercises/06_code_quality/archive/testdata/boy-2.png new file mode 100644 index 00000000..094d3596 Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/boy-2.png differ diff --git a/exercises/06_code_quality/archive/testdata/boy-3.png b/exercises/06_code_quality/archive/testdata/boy-3.png new file mode 100644 index 00000000..05da0fa5 Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/boy-3.png differ diff --git a/exercises/06_code_quality/archive/testdata/boy-4.png b/exercises/06_code_quality/archive/testdata/boy-4.png new file mode 100644 index 00000000..49d35dfc Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/boy-4.png differ diff --git a/exercises/06_code_quality/archive/testdata/boy-5.png b/exercises/06_code_quality/archive/testdata/boy-5.png new file mode 100644 index 00000000..b7f6c3ce Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/boy-5.png differ diff --git a/exercises/06_code_quality/archive/testdata/boy.png b/exercises/06_code_quality/archive/testdata/boy.png new file mode 100644 index 00000000..bd28bfeb Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/boy.png differ diff --git a/exercises/06_code_quality/archive/testdata/girl-2.png b/exercises/06_code_quality/archive/testdata/girl-2.png new file mode 100644 index 00000000..abb45ece Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/girl-2.png differ diff --git a/exercises/06_code_quality/archive/testdata/girl-3.png b/exercises/06_code_quality/archive/testdata/girl-3.png new file mode 100644 index 00000000..d0dc6200 Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/girl-3.png differ diff --git a/exercises/06_code_quality/archive/testdata/girl-4.png b/exercises/06_code_quality/archive/testdata/girl-4.png new file mode 100644 index 00000000..9822f81a Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/girl-4.png differ diff --git a/exercises/06_code_quality/archive/testdata/girl-5.png b/exercises/06_code_quality/archive/testdata/girl-5.png new file mode 100644 index 00000000..16af9a00 Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/girl-5.png differ diff --git a/exercises/06_code_quality/archive/testdata/girl.png b/exercises/06_code_quality/archive/testdata/girl.png new file mode 100644 index 00000000..831202b2 Binary files /dev/null and b/exercises/06_code_quality/archive/testdata/girl.png differ diff --git a/exercises/06_code_quality/index.js b/exercises/06_code_quality/index.js new file mode 100644 index 00000000..43c2fcd1 --- /dev/null +++ b/exercises/06_code_quality/index.js @@ -0,0 +1,115 @@ +#!/usr/bin/env node + +/** + * Routes File + */ + +'use strict' + +/* MODULE IMPORTS */ +const bcrypt = require('bcrypt-promise') +const Koa = require('koa') +const Router = require('koa-router') +const views = require('koa-views') +const staticDir = require('koa-static') +const bodyParser = require('koa-bodyparser') +const koaBody = require('koa-body')({multipart: true, uploadDir: '.'}) +const session = require('koa-session') +const sqlite = require('sqlite-async') +const fs = require('fs-extra') +const mime = require('mime-types') +//const jimp = require('jimp') + +/* IMPORT CUSTOM MODULES */ +const User = require('./modules/accounts') + +const app = new Koa() +const router = new Router() + +/* CONFIGURING THE MIDDLEWARE */ +app.keys = ['darkSecret'] +app.use(staticDir('public')) +app.use(bodyParser()) +app.use(session(app)) +app.use(views(`${__dirname}/views`, { extension: 'handlebars' }, {map: { handlebars: 'handlebars' }})) + +const defaultPort = 8080 +const port = process.env.PORT || defaultPort +const dbName = 'website.db' +const saltRounds = 10 + +/** + * The secure home page. + * + * @name Home Page + * @route {GET} / + * @authentication This route requires cookie-based authentication. + */ +router.get('/', async ctx => { + try { + if(ctx.session.authorised !== true) return ctx.redirect('/login?msg=you need to log in') + const data = {} + if(ctx.query.msg) data.msg = ctx.query.msg + await ctx.render('index') + } catch(err) { + await ctx.render('error', {message: err.message}) + } +}) + +/** + * The user registration page. + * + * @name Register Page + * @route {GET} /register + */ +router.get('/register', async ctx => await ctx.render('register')) + +/** + * The script to process new user registrations. + * + * @name Register Script + * @route {POST} /register + */ +router.post('/register', koaBody, async ctx => { + try { + // extract the data from the request + const body = ctx.request.body + console.log(body) + const {path, type} = ctx.request.files.avatar + // 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}) + } +}) + +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) +}) + +router.post('/login', async ctx => { + try { + const body = ctx.request.body + 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) { + await ctx.render('error', {message: err.message}) + } +}) + +router.get('/logout', async ctx => { + ctx.session.authorised = null + ctx.redirect('/?msg=you are now logged out') +}) + +app.use(router.routes()) +module.exports = app.listen(port, async() => console.log(`listening on port ${port}`)) diff --git a/exercises/06_code_quality/jest-test.config.js b/exercises/06_code_quality/jest-test.config.js new file mode 100644 index 00000000..220e31c9 --- /dev/null +++ b/exercises/06_code_quality/jest-test.config.js @@ -0,0 +1,19 @@ +'use strict' + +module.exports = { + displayName: 'test', + verbose: true, + collectCoverage: true, + coverageThreshold: { + global: { + branches: 0, + functions: 0, + lines: 0, + statements: 0 + } + }, + testPathIgnorePatterns: [ + '/node_modules/', + '/__tests__/fixtures/', + ] +} diff --git a/exercises/06_code_quality/jsdoc.conf b/exercises/06_code_quality/jsdoc.conf new file mode 100644 index 00000000..9fa536f2 --- /dev/null +++ b/exercises/06_code_quality/jsdoc.conf @@ -0,0 +1,22 @@ + +{ + "tags": { + "allowUnknownTags": true, + "dictionaries": ["jsdoc","closure"] + }, + "source": { + "include": [ "." ], + "exclude": [ "node_modules" ], + "includePattern": ".+\\.js(doc|x)?$", + "excludePattern": "(^|\\/|\\\\)_" + }, + "plugins": ["jsdoc-route-plugin"], + "templates": { + "cleverLinks": false, + "monospaceLinks": false + }, + "opts": { + "destination": "docs/jsdoc", + "recurse": true + } +} diff --git a/exercises/06_code_quality/modules/accounts.js b/exercises/06_code_quality/modules/accounts.js new file mode 100644 index 00000000..fd1c7ec4 --- /dev/null +++ b/exercises/06_code_quality/modules/accounts.js @@ -0,0 +1,60 @@ + +'use strict' + +const bcrypt = require('bcrypt-promise') +const fs = require('fs-extra') +const mime = require('mime-types') +const sqlite = require('sqlite-async') +const saltRounds = 10 + +module.exports = class User { + + constructor(dbName = ':memory:') { + return (async() => { + this.db = await sqlite.open(dbName) + // we need this table to store the user accounts + const sql = 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT, pass TEXT);' + await this.db.run(sql) + return this + })() + } + + 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) { + 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/exercises/06_code_quality/package.json b/exercises/06_code_quality/package.json new file mode 100644 index 00000000..6c5393de --- /dev/null +++ b/exercises/06_code_quality/package.json @@ -0,0 +1,47 @@ +{ + "name": "10_auth", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engines": { + "node": "12.x" + }, + "scripts": { + "start": "node index.js", + "acceptance": "jest --coverage --detectOpenHandles", + "jsdoc": "node_modules/.bin/jsdoc -c jsdoc.conf", + "linter": "node_modules/.bin/eslint .", + "test": "jest --coverage --detectOpenHandles", + "unit": "node_modules/.bin/jest --coverage --runInBand tests/unit/" + }, + "jest": { + "projects": [ + "/jest-test.config.js" + ] + }, + "author": "", + "license": "ISC", + "dependencies": { + "bcrypt": "^3.0.6", + "bcrypt-promise": "^2.0.0", + "fs-extra": "^7.0.1", + "handlebars": "^4.1.2", + "koa": "^2.6.2", + "koa-body": "^4.0.8", + "koa-bodyparser": "^4.2.1", + "koa-router": "^7.4.0", + "koa-session": "^5.10.1", + "koa-static": "^5.0.0", + "koa-views": "^6.1.5", + "mime-types": "^2.1.22", + "sqlite-async": "^1.0.11" + }, + "devDependencies": { + "eslint": "^5.15.2", + "handlebars-validate": "^0.1.2", + "http-status-codes": "^1.3.2", + "jest": "^24.1.0", + "jsdoc": "^3.6.3", + "jsdoc-route-plugin": "^0.1.0" + } +} diff --git a/exercises/06_code_quality/public/avatars/avatar.png b/exercises/06_code_quality/public/avatars/avatar.png new file mode 100644 index 00000000..125a3893 Binary files /dev/null and b/exercises/06_code_quality/public/avatars/avatar.png differ diff --git a/exercises/06_code_quality/public/style.css b/exercises/06_code_quality/public/style.css new file mode 100644 index 00000000..91b57140 --- /dev/null +++ b/exercises/06_code_quality/public/style.css @@ -0,0 +1,12 @@ + +body { + font-family: Arial, Helvetica, sans-serif; + } + + .msg { + border: 1px solid red; + font-weight: bold; + color: red; + padding: 1em; + } + \ No newline at end of file diff --git a/exercises/06_code_quality/unit tests/user.spec.js b/exercises/06_code_quality/unit tests/user.spec.js new file mode 100644 index 00000000..886d3440 --- /dev/null +++ b/exercises/06_code_quality/unit tests/user.spec.js @@ -0,0 +1,76 @@ + +'use strict' + +const Accounts = require('../modules/user.js') + +describe('register()', () => { + + test('register a valid account', async done => { + expect.assertions(1) + const account = await new Accounts() + const register = await account.register('doej', 'password') + expect(register).toBe(true) + 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', async done => { + expect.assertions(1) + 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() + }) + +}) diff --git a/exercises/06_code_quality/views/error.handlebars b/exercises/06_code_quality/views/error.handlebars new file mode 100644 index 00000000..11fe16d0 --- /dev/null +++ b/exercises/06_code_quality/views/error.handlebars @@ -0,0 +1,16 @@ + + + + + + + ERROR + + + + + +

An Error Has Occurred

+

{{message}}

+ + diff --git a/exercises/06_code_quality/views/index.handlebars b/exercises/06_code_quality/views/index.handlebars new file mode 100644 index 00000000..5d9be040 --- /dev/null +++ b/exercises/06_code_quality/views/index.handlebars @@ -0,0 +1,16 @@ + + + + + + + Home Page + + + + + +

Home

+

This is a secure page. Users need to have a valid account and be logged in to see it.

+

log out

+ diff --git a/exercises/06_code_quality/views/login.handlebars b/exercises/06_code_quality/views/login.handlebars new file mode 100644 index 00000000..e42e65bd --- /dev/null +++ b/exercises/06_code_quality/views/login.handlebars @@ -0,0 +1,24 @@ + + + + + + + Log In + + + + + +

Log In

+ {{#if msg}} +

{{msg}}

+ {{/if}} +
+

Username:

+

Password:

+

+
+

register

+ + diff --git a/exercises/06_code_quality/views/register.handlebars b/exercises/06_code_quality/views/register.handlebars new file mode 100644 index 00000000..2e543b83 --- /dev/null +++ b/exercises/06_code_quality/views/register.handlebars @@ -0,0 +1,21 @@ + + + + + + + Create an Account + + + + + +

Create an Account

+
+

Username:

+

Password:

+

Profile Picture:

+

+
+ +