diff --git a/02 Forms and Authentication.md b/02 Forms and Authentication.md index 15fc4fc..e398835 100644 --- a/02 Forms and Authentication.md +++ b/02 Forms and Authentication.md @@ -83,21 +83,18 @@ So how can we improve this security? The simplest way is to use AES256-GCM encry In this section we will be implementing more granular authorisation by creating an admin-only area on the website: -1. Add an extra boolean field to the database called `admin` with a default value of `false`. -2. Manually modify one of your registered accounts and change the stored value to `true`, this will be your admin user. -3. When a user logs in, check this field and, if it is set to true, create a new session variable called `admin` and assign it a value of `true`. -4. Create a new admin page, `admin.handlebars` and make sure it is protected in the same way as the home page. -5. Create a link to this new page from the homepage. -6. Modify the page so that, if the user does not have admin authorisation they get redirected back to the home page which should display a message **You do not have admin privileges!**. - -## 5 Extension Tasks - -Congratulations, you now have a working secure website shell which can be used as the base code for your coursework. You should now consider the following: - -1. Start by encrypting your cookies using an appropriate npm package. -2. The website is currently using a relational database (SQLite). If you plan on using a different form of persistence such as a document database (eg MongoDB) or a graph database (eg Neo4J) this is the time to make the change. Make sure the existing functionality works with your chosen persistence technology. -3. In your last lab you learned how to build list and details pages and connect these together. Read your assignment brief carefully and decide if and how you will implement these. You should start work on this part of your project at the earliest opportunity. -4. You will need to implement one or more forms as part of your assignment. Analyse the problem and build template files containing your required forms. Also implement the code required to process this data and add it to your preferred database. +1. Create a secure admin page: + 1. Add an extra boolean field to the database called `admin` with a default value of `false`. + 2. Manually modify one of your registered accounts and change the stored value to `true`, this will be your admin user. + 3. When a user logs in, check this field and, if it is set to true, create a new session variable called `admin` and assign it a value of `true`. + 4. Create a new admin page, `admin.handlebars` and make sure it is protected in the same way as the home page. + 5. Create a link to this new page from the homepage. + 6. Modify the page so that, if the user does not have admin authorisation they get redirected back to the home page which should display a message **You do not have admin privileges!**. +2. Modify the secure pages (home and admin) so that they displays the username of the currently logged-in user: + 1. Start by modifying the `post('login')` callback, adding a second key called `username` to the `ctx.session` object to store the username. + 2. Modify the code that logs the user out to remove this key. + 3. Now change the callback for the secure home page so that it sends this data to the handlebars template. + 4. Finally create a placeholder in the template to display this information. ## Advanced Topics diff --git a/03 Acceptance Testing.md b/03 Acceptance Testing.md index 82b35cf..0923ece 100644 --- a/03 Acceptance Testing.md +++ b/03 Acceptance Testing.md @@ -94,9 +94,25 @@ You have seen how to use Puppeteer to write a test to see that we can add a sing ### 1.2 Snapshots +The acceptance tests in the previous section interact with the [Document Model Object](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), they have no way to understand the appearance of the page as this is influenced by the [Cascading Stylesheets](https://developer.mozilla.org/en-US/docs/Web/CSS) that are being applied to the page. Clearly it is important that our acceptance tests should also check that the page renders as it should and [Snapshot Testing](https://jestjs.io/docs/en/snapshot-testing) enables us to achieve this. + +In essence we identify the important screens in the app (the ones we wish to monitor), get the layout the way we expect it to be and capture a screenshot of the page which is stored in a `__image_snapshots_/` directory then used as a reference whenever the test suite is subsequently run. Since you have already triggered an inital test run you should be able to locate this directory and examine the image file it contains. + +The snapshot is captured using the following two lines of code: + +```javascript +const image = await page.screenshot() +expect(image).toMatchImageSnapshot() +``` + +Sometimes you will need to modify the page layout however this will cause the snapshot test to fail since there will no longer be a match. The solution is to run the test suite but add a `-u` flag. This will update the snapshot files. + #### 1.2.1 Test Your Understanding -xxx +1. Modify the stylesheet and make the top level heading larger (perhaps `28pt`). +2. Now re-run the acceptance test suite, this will fail because the layout is now different. +3. Edit the `test.sh` script, adding the `-u` flag, save this change and rerun the test suite. +4. Remove the `-u` flag and rerun the tests to check these are still passing. ## 2 Profiling @@ -129,7 +145,9 @@ There are many more settings you can experiment with to help understand the perf ### 2.1 Test Your Understanding -xxx +1. Look at the graphs and figures that are produced for the todo code profiling and identify the different stages and different time taken for each. Are there any stages where you feel the timing is significantly long. +2. Run the todo code three times and compare the time taken for each of stages involved. Do they different? Is there any reason why this might be the case? +3. Using the har analyzer how long does it take for the server to respond. ### 2.2 Flame Graphs @@ -174,7 +192,8 @@ Hovering over a box will reveal more information. ### 2.2.1 Test Your Understanding -xxx +1. Looking at the flame graph which process is consuming the most CPU cycles? Is it possible to optimise this process to reduce the cycles? +2. Looking at the flame graph which process what is the hot path for the code. ## 3 Cucumber diff --git a/exercises/01_dynamic_websites/bookshop.db b/exercises/01_dynamic_websites/bookshop.db index 3f67c81..9301af6 100755 Binary files a/exercises/01_dynamic_websites/bookshop.db and b/exercises/01_dynamic_websites/bookshop.db differ diff --git a/exercises/03_acceptance/cucumber/cucumberTest.sh b/exercises/03_acceptance/cucumber/cucumberTest.sh index cb5dab2..98b63d2 100755 --- a/exercises/03_acceptance/cucumber/cucumberTest.sh +++ b/exercises/03_acceptance/cucumber/cucumberTest.sh @@ -1,6 +1,25 @@ #!/usr/bin/env bash +# force the script to exit on first fail +set -e + +# create any directories needed by the test script +mkdir -p screenshots + +# delete any local databases (if you are using them) +rm -rf *.db + +# install packages if none found +# [ ! -d "node_modules" ] && echo "INSTALLING MODULES" && npm install + +# start the web server in background mode node index.js& + +# run the test suite in background mode node_modules/.bin/cucumber-js ./features -r ./steps & -sleep 2 + +# wait for the tests to complete +sleep 5 + +# kill the web server pkill node diff --git a/exercises/03_acceptance/cucumber/features/add.feature b/exercises/03_acceptance/cucumber/features/add.feature new file mode 100644 index 0000000..e187c3d --- /dev/null +++ b/exercises/03_acceptance/cucumber/features/add.feature @@ -0,0 +1,23 @@ +Feature: Adding Items + The user should be able to add items to the list. + + Scenario: add single item + Given The browser is open on the home page + When I enter "bread" in the "item" field + When I enter "42" in the "qty" field + When I click on the submit button + Then the heading should be "ToDo List" + Then the list should contain "1" rows + Then the list should contain a single entry for "bread" + Then the item should be "bread" + Then the "bread" quantity should be "42" + + Scenario: add multiple items + Given The browser is open on the home page + When I enter "butter" in the "item" field + When I enter "24" in the "qty" field + When I click on the submit button + Then the heading should be "ToDo List" + Then the list should contain "2" rows + Then the list should contain a single entry for "bread" + Then the item should be "bread" diff --git a/exercises/03_acceptance/cucumber/features/add_one.feature b/exercises/03_acceptance/cucumber/features/add_one.feature deleted file mode 100644 index 1b85d07..0000000 --- a/exercises/03_acceptance/cucumber/features/add_one.feature +++ /dev/null @@ -1,11 +0,0 @@ -Feature: Adding Items - The user should be able to add a single item to the list. - - Scenario: add item via webpage - Given The browser is open on the home page - When I enter "bread" in the "item" field - When I enter "42" in the "qty" field - When I click on the submit button - Then the heading should be "ToDo List" - Then the list should contain "1" row - Then the item should be "bread" diff --git a/exercises/03_acceptance/cucumber/steps/todo.steps.js b/exercises/03_acceptance/cucumber/steps/todo.steps.js index 5080004..682e1e3 100644 --- a/exercises/03_acceptance/cucumber/steps/todo.steps.js +++ b/exercises/03_acceptance/cucumber/steps/todo.steps.js @@ -41,6 +41,10 @@ When('I click on the submit button', async() => { await page.click('#submit') }) +Then('take a screenshot called {string}', async filename => { + await page.screenshot({ path: `screenshots/${filename}.png` }) +}) + Then('the heading should be {string}', async heading => { const text = await page.evaluate( () => { const dom = document.querySelector('h1') @@ -49,7 +53,8 @@ Then('the heading should be {string}', async heading => { assert.equal(heading, text) }) -Then('the list should contain {string} row', async rowCount => { +Then('the list should contain {string} rows', async rowCount => { + rowCount = Number(rowCount) const items = await page.evaluate( () => { const dom = document.querySelectorAll('table tr td:first-child') const arr = Array.from(dom) @@ -66,3 +71,22 @@ Then('the item should be {string}', async item => { }) assert.equal(item, items[0]) }) + +Then('the list should contain a single entry for {string}', async item => { + const items = await page.evaluate( () => { + const dom = document.querySelectorAll('table tr td:first-child') + const arr = Array.from(dom).map(td => td.innerText) + return arr + }) + const count = items.reduce( (acc, val) => (val === item ? acc += 1 : acc), 0) + assert.equal(count, 1) +}) + +Then('the {string} quantity should be {string}', async(item, qty) => { + const items = await page.evaluate( () => { + const dom = document.querySelectorAll('table tr') + // const arr = Array.from(dom) + return dom + }) + assert.equal(2, 2) +}) diff --git a/exercises/03_acceptance/todo/acceptance tests/ui.test.js b/exercises/03_acceptance/todo/acceptance tests/ui.test.js index 0f0a964..a794b45 100644 --- a/exercises/03_acceptance/todo/acceptance tests/ui.test.js +++ b/exercises/03_acceptance/todo/acceptance tests/ui.test.js @@ -68,20 +68,26 @@ describe('todo list', () => { const arr = Array.from(dom) return arr.map(td => td.innerText) }) + const numRows = items.reduce( (acc, val) => (val === 'bread' ? acc += 1 : acc), 0) - // this is a more concise way to achieve the same result... + console.log(`numRows: ${numRows}`) + + // returns the number of rows containing the given string const items2 = await page.evaluate(() => Array .from(document.querySelectorAll('table tr td:first-child')) - .map(td => td.innerHTML) ) + .map(td => td.innerHTML) + .reduce( (acc, val) => (val === 'bread' ? acc += 1 : acc), 0) + ) expect(items.length).toBe(1) expect(items[0]).toBe('bread') + expect(numRows).toBe(1) // grab a screenshot const image = await page.screenshot() // compare to the screenshot from the previous test run expect(image).toMatchImageSnapshot() - // stop logging to the trace file + // stop logging to the trace files await page.tracing.stop() await har.stop() done() diff --git a/exercises/03_acceptance/todo/package.json b/exercises/03_acceptance/todo/package.json index 7f99bd8..3b1f5df 100644 --- a/exercises/03_acceptance/todo/package.json +++ b/exercises/03_acceptance/todo/package.json @@ -4,11 +4,13 @@ "description": "", "main": "index.js", "scripts": { + "start-server": "node index.js", "linter": "node_modules/.bin/eslint .", - "test": "node_modules/.bin/jest --coverage --runInBand --detectOpenHandles", + "test": "./node_modules/.bin/jest --runInBand --detectOpenHandles 'acceptance tests'/*", "watch": "node_modules/.bin/jest --coverage --watchAll", "acceptance": "./test.sh", - "profiler": "./node_modules/.bin/0x -o index.js" + "profiler": "./node_modules/.bin/0x -o index.js", + "ci": "./node_modules/.bin/start-server-and-test start-server http://localhost:8080 test" }, "author": "", "license": "ISC", @@ -30,6 +32,7 @@ "jest-puppeteer": "^4.3.0", "puppeteer": "^1.20.0", "puppeteer-har": "^1.1.1", + "start-server-and-test": "^1.10.6", "supertest": "^4.0.2" }, "jest": { diff --git a/exercises/03_acceptance/todo/public/style.css b/exercises/03_acceptance/todo/public/style.css index 8a5442c..d38f05f 100644 --- a/exercises/03_acceptance/todo/public/style.css +++ b/exercises/03_acceptance/todo/public/style.css @@ -3,6 +3,10 @@ body { font-family: Arial, Helvetica, sans-serif; } +h1 { + font-size: 18pt +} + .msg { border: 1px solid red; font-weight: bold; diff --git a/exercises/03_acceptance/todo/test.sh b/exercises/03_acceptance/todo/test.sh index 10fae29..3249ddd 100755 --- a/exercises/03_acceptance/todo/test.sh +++ b/exercises/03_acceptance/todo/test.sh @@ -1,7 +1,13 @@ #!/usr/bin/env bash -mkdir screenshots -mkdir trace +set -e +echo hello +mkdir -p screenshots +mkdir -p trace +rm -rf *.db +# [ ! -d "node_modules" ] && echo "INSTALLING MODULES" && npm install node index.js& node_modules/.bin/jest --runInBand --detectOpenHandles acceptance\ tests/* kill %1 +read -p "Press enter to continue" + diff --git a/exercises/05_mqtt/chat/Messages.js b/exercises/05_mqtt/chat/Messages.js new file mode 100644 index 0000000..8457de4 --- /dev/null +++ b/exercises/05_mqtt/chat/Messages.js @@ -0,0 +1,19 @@ + +'use strict' + +module.exports = class Messages { + constructor() { + console.log('constructor') + this.messages = [] + return this + } + extractData() { + + } + add() { + + } + getAll() { + + } +} diff --git a/exercises/05_mqtt/chat/module.js b/exercises/05_mqtt/chat/module.js index 947f4c7..d9b8421 100755 --- a/exercises/05_mqtt/chat/module.js +++ b/exercises/05_mqtt/chat/module.js @@ -7,6 +7,7 @@ const messages = ( function() { let msgs = [] return { extractData: payloadString => { + console.log('extracData') return payloadString }, add: data => {