Add personal library certification project
This commit is contained in:
parent
2c60f17921
commit
6efa7707a4
16 changed files with 5929 additions and 0 deletions
2
7-quality-assurance/3-library/.gitattributes
vendored
Normal file
2
7-quality-assurance/3-library/.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Tell Linguist to exclude HTML files to help Replit language detection.
|
||||||
|
*.html linguist-vendored
|
3
7-quality-assurance/3-library/.gitignore
vendored
Normal file
3
7-quality-assurance/3-library/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.vscode/*
|
||||||
|
.env
|
||||||
|
node_modules/
|
1
7-quality-assurance/3-library/.prettierignore
Normal file
1
7-quality-assurance/3-library/.prettierignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.replit
|
10
7-quality-assurance/3-library/.replit
Normal file
10
7-quality-assurance/3-library/.replit
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
run = "npm start"
|
||||||
|
entrypoint = "server.js"
|
||||||
|
|
||||||
|
[packager]
|
||||||
|
language = "nodejs"
|
||||||
|
|
||||||
|
[packager.features]
|
||||||
|
packageSearch = true
|
||||||
|
guessImports = true
|
||||||
|
enabledForHosting = false
|
3
7-quality-assurance/3-library/README.md
Normal file
3
7-quality-assurance/3-library/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Personal Library
|
||||||
|
|
||||||
|
This is the boilerplate for the Personal Library project. Instructions for building your project can be found at https://www.freecodecamp.org/learn/quality-assurance/quality-assurance-projects/personal-library
|
139
7-quality-assurance/3-library/assertion-analyser.js
Normal file
139
7-quality-assurance/3-library/assertion-analyser.js
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* DO NOT EDIT THIS FILE
|
||||||
|
* For FCC testing purposes!
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
function objParser(str, init) {
|
||||||
|
// finds objects, arrays, strings, and function arguments
|
||||||
|
// between parens, because they may contain ','
|
||||||
|
let openSym = ['[', '{', '"', "'", '('];
|
||||||
|
let closeSym = [']', '}', '"', "'", ')'];
|
||||||
|
let type;
|
||||||
|
let i;
|
||||||
|
for(i = (init || 0); i < str.length; i++ ) {
|
||||||
|
type = openSym.indexOf(str[i]);
|
||||||
|
if( type !== -1) break;
|
||||||
|
}
|
||||||
|
if (type === -1) return null;
|
||||||
|
let open = openSym[type];
|
||||||
|
let close = closeSym[type];
|
||||||
|
let count = 1;
|
||||||
|
let k;
|
||||||
|
for(k = i+1; k < str.length; k++) {
|
||||||
|
if(open === '"' || open === "'") {
|
||||||
|
if(str[k] === close) count--;
|
||||||
|
if(str[k] === '\\') k++;
|
||||||
|
} else {
|
||||||
|
if(str[k] === open) count++;
|
||||||
|
if(str[k] === close) count--;
|
||||||
|
}
|
||||||
|
if(count === 0) break;
|
||||||
|
}
|
||||||
|
if(count !== 0) return null;
|
||||||
|
let obj = str.slice(i, k+1);
|
||||||
|
return {
|
||||||
|
start : i,
|
||||||
|
end: k,
|
||||||
|
obj: obj
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function replacer(str) {
|
||||||
|
// replace objects with a symbol ( __#n)
|
||||||
|
let obj;
|
||||||
|
let cnt = 0;
|
||||||
|
let data = [];
|
||||||
|
while(obj = objParser(str)) {
|
||||||
|
data[cnt] = obj.obj;
|
||||||
|
str = str.substring(0, obj.start) + '__#' + cnt++ + str.substring(obj.end+1)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
str : str,
|
||||||
|
dictionary : data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitter(str) {
|
||||||
|
// split on commas, then restore the objects
|
||||||
|
let strObj = replacer(str);
|
||||||
|
let args = strObj.str.split(',');
|
||||||
|
args = args.map(function(a){
|
||||||
|
let m = a.match(/__#(\d+)/);
|
||||||
|
while (m) {
|
||||||
|
a = a.replace(/__#(\d+)/, strObj.dictionary[m[1]]);
|
||||||
|
m = a.match(/__#(\d+)/);
|
||||||
|
}
|
||||||
|
return a.trim();
|
||||||
|
})
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertionAnalyser(body) {
|
||||||
|
|
||||||
|
// already filtered in the test runner
|
||||||
|
// // remove comments
|
||||||
|
// body = body.replace(/\/\/.*\n|\/\*.*\*\//g, '');
|
||||||
|
// // get test function body
|
||||||
|
// body = body.match(/\{\s*([\s\S]*)\}\s*$/)[1];
|
||||||
|
|
||||||
|
if(!body) return "invalid assertion";
|
||||||
|
// replace assertions bodies, so that they cannot
|
||||||
|
// contain the word 'assertion'
|
||||||
|
|
||||||
|
let cleanedBody = body.match(/(?:browser\s*\.\s*)?assert\s*\.\s*\w*\([\s\S]*\)/)
|
||||||
|
if(cleanedBody && Array.isArray(cleanedBody)) {
|
||||||
|
body = cleanedBody[0];
|
||||||
|
} else {
|
||||||
|
// No assertions present
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let s = replacer(body);
|
||||||
|
// split on 'assertion'
|
||||||
|
let splittedAssertions = s.str.split('assert');
|
||||||
|
let assertions = splittedAssertions.slice(1);
|
||||||
|
// match the METHODS
|
||||||
|
|
||||||
|
let assertionBodies = [];
|
||||||
|
let methods = assertions.map(function(a, i){
|
||||||
|
let m = a.match(/^\s*\.\s*(\w+)__#(\d+)/);
|
||||||
|
assertionBodies.push(parseInt(m[2]));
|
||||||
|
let pre = splittedAssertions[i].match(/browser\s*\.\s*/) ? 'browser.' : '';
|
||||||
|
return pre + m[1];
|
||||||
|
});
|
||||||
|
if(methods.some(function(m){ return !m })) return "invalid assertion";
|
||||||
|
// remove parens from the assertions bodies
|
||||||
|
let bodies = assertionBodies.map(function(b){
|
||||||
|
return s.dictionary[b].slice(1,-1).trim();
|
||||||
|
});
|
||||||
|
assertions = methods.map(function(m, i) {
|
||||||
|
return {
|
||||||
|
method: m,
|
||||||
|
args: splitter(bodies[i]) //replace objects, split on ',' ,then restore objects
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return assertions;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = assertionAnalyser;
|
4968
7-quality-assurance/3-library/package-lock.json
generated
Normal file
4968
7-quality-assurance/3-library/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
30
7-quality-assurance/3-library/package.json
Normal file
30
7-quality-assurance/3-library/package.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "fcc-library",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "boilerplate",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.15.2",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"chai-http": "^4.3.0",
|
||||||
|
"cors": "^2.8.1",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"express": "^4.14.0",
|
||||||
|
"mocha": "^3.2.0",
|
||||||
|
"mongoose": "^6.7.1",
|
||||||
|
"zombie": "^5.0.5"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/freeCodeCamp/boilerplate-project-library"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"node",
|
||||||
|
"hyperdev",
|
||||||
|
"express"
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
85
7-quality-assurance/3-library/public/client.js
Normal file
85
7-quality-assurance/3-library/public/client.js
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
$( document ).ready(function() {
|
||||||
|
let items = [];
|
||||||
|
let itemsRaw = [];
|
||||||
|
|
||||||
|
$.getJSON('/api/books', function(data) {
|
||||||
|
//let items = [];
|
||||||
|
itemsRaw = data;
|
||||||
|
$.each(data, function(i, val) {
|
||||||
|
items.push('<li class="bookItem" id="' + i + '">' + val.title + ' - ' + val.commentcount + ' comments</li>');
|
||||||
|
return ( i !== 14 );
|
||||||
|
});
|
||||||
|
if (items.length >= 15) {
|
||||||
|
items.push('<p>...and '+ (data.length - 15)+' more!</p>');
|
||||||
|
}
|
||||||
|
$('<ul/>', {
|
||||||
|
'class': 'listWrapper',
|
||||||
|
html: items.join('')
|
||||||
|
}).appendTo('#display');
|
||||||
|
});
|
||||||
|
|
||||||
|
let comments = [];
|
||||||
|
$('#display').on('click','li.bookItem',function() {
|
||||||
|
$("#detailTitle").html('<b>'+itemsRaw[this.id].title+'</b> (id: '+itemsRaw[this.id]._id+')');
|
||||||
|
$.getJSON('/api/books/'+itemsRaw[this.id]._id, function(data) {
|
||||||
|
comments = [];
|
||||||
|
$.each(data.comments, function(i, val) {
|
||||||
|
comments.push('<li>' +val+ '</li>');
|
||||||
|
});
|
||||||
|
comments.push('<br><form id="newCommentForm"><input style="width:300px" type="text" class="form-control" id="commentToAdd" name="comment" placeholder="New Comment"></form>');
|
||||||
|
comments.push('<br><button class="btn btn-info addComment" id="'+ data._id+'">Add Comment</button>');
|
||||||
|
comments.push('<button class="btn btn-danger deleteBook" id="'+ data._id+'">Delete Book</button>');
|
||||||
|
$('#detailComments').html(comments.join(''));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#bookDetail').on('click','button.deleteBook',function() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/books/'+this.id,
|
||||||
|
type: 'delete',
|
||||||
|
success: function(data) {
|
||||||
|
//update list
|
||||||
|
$('#detailComments').html('<p style="color: red;">'+data+'<p><p>Refresh the page</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#bookDetail').on('click','button.addComment',function() {
|
||||||
|
let newComment = $('#commentToAdd').val();
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/books/'+this.id,
|
||||||
|
type: 'post',
|
||||||
|
dataType: 'json',
|
||||||
|
data: $('#newCommentForm').serialize(),
|
||||||
|
success: function(data) {
|
||||||
|
comments.unshift(newComment); //adds new comment to top of list
|
||||||
|
$('#detailComments').html(comments.join(''));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#newBook').click(function() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/books',
|
||||||
|
type: 'post',
|
||||||
|
dataType: 'json',
|
||||||
|
data: $('#newBookForm').serialize(),
|
||||||
|
success: function(data) {
|
||||||
|
//update list
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#deleteAllBooks').click(function() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/books',
|
||||||
|
type: 'delete',
|
||||||
|
dataType: 'json',
|
||||||
|
data: $('#newBookForm').serialize(),
|
||||||
|
success: function(data) {
|
||||||
|
//update list
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
34
7-quality-assurance/3-library/public/style.css
Normal file
34
7-quality-assurance/3-library/public/style.css
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
.border {
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sampleui {
|
||||||
|
max-width: 450px;
|
||||||
|
margin-left: 5%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sampleposting {
|
||||||
|
max-width: 450px;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#userstories {
|
||||||
|
margin-left: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
95
7-quality-assurance/3-library/routes/api.js
Normal file
95
7-quality-assurance/3-library/routes/api.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Complete the API routing below
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = function (app, Books) {
|
||||||
|
app
|
||||||
|
.route("/api/books")
|
||||||
|
.get(function (req, res) {
|
||||||
|
Books.find((err, docs) => {
|
||||||
|
if (err) return console.log(err);
|
||||||
|
const response = [];
|
||||||
|
docs.map((v) => {
|
||||||
|
response.push({
|
||||||
|
_id: v._id,
|
||||||
|
title: v.title,
|
||||||
|
commentcount: v.comments.length,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
res.json(response);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
.post(function (req, res) {
|
||||||
|
let title = req.body.title;
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
return res.json("missing required field title");
|
||||||
|
}
|
||||||
|
Books.create({ title: title }, (err, doc) => {
|
||||||
|
if (err) return console.log(err);
|
||||||
|
res.json({ title: doc.title, _id: doc._id });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
.delete(function (req, res) {
|
||||||
|
Books.deleteMany((err) => {
|
||||||
|
if (err) return console.log(err);
|
||||||
|
res.json("complete delete successful");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app
|
||||||
|
.route("/api/books/:id")
|
||||||
|
.get(function (req, res) {
|
||||||
|
let bookid = req.params.id;
|
||||||
|
if (!bookid) {
|
||||||
|
return res.json("missing required field id");
|
||||||
|
}
|
||||||
|
Books.findById(bookid, (err, doc) => {
|
||||||
|
if (!doc) return res.json("no book exists");
|
||||||
|
res.json(doc);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
.post(function (req, res) {
|
||||||
|
let bookid = req.params.id;
|
||||||
|
let comment = req.body.comment;
|
||||||
|
if (!bookid) {
|
||||||
|
return res.json("missing required field id");
|
||||||
|
}
|
||||||
|
if (!comment) {
|
||||||
|
return res.json("missing required field comment");
|
||||||
|
}
|
||||||
|
Books.findByIdAndUpdate(
|
||||||
|
bookid,
|
||||||
|
{
|
||||||
|
$push: {
|
||||||
|
comments: comment,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ new: true },
|
||||||
|
(err, doc) => {
|
||||||
|
if (!doc) return res.json("no book exists");
|
||||||
|
res.json(doc);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
.delete(function (req, res) {
|
||||||
|
let bookid = req.params.id;
|
||||||
|
if (!bookid) {
|
||||||
|
return res.json("no book exists");
|
||||||
|
}
|
||||||
|
Books.findByIdAndDelete(bookid, (err, doc) => {
|
||||||
|
if (!doc) return res.json("no book exists");
|
||||||
|
res.json("delete successful");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
94
7-quality-assurance/3-library/routes/fcctesting.js
Normal file
94
7-quality-assurance/3-library/routes/fcctesting.js
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* DO NOT EDIT THIS FILE
|
||||||
|
* For FCC testing purposes!
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const cors = require('cors');
|
||||||
|
const fs = require('fs');
|
||||||
|
const runner = require('../test-runner');
|
||||||
|
|
||||||
|
module.exports = function (app) {
|
||||||
|
|
||||||
|
app.route('/_api/server.js')
|
||||||
|
.get(function(req, res, next) {
|
||||||
|
console.log('requested');
|
||||||
|
fs.readFile(__dirname + '/server.js', function(err, data) {
|
||||||
|
if(err) return next(err);
|
||||||
|
res.send(data.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.route('/_api/routes/api.js')
|
||||||
|
.get(function(req, res, next) {
|
||||||
|
console.log('requested');
|
||||||
|
fs.readFile(__dirname + '/routes/api.js', function(err, data) {
|
||||||
|
if(err) return next(err);
|
||||||
|
res.type('txt').send(data.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/_api/get-tests', cors(), function(req, res, next){
|
||||||
|
console.log('requested');
|
||||||
|
if(process.env.NODE_ENV === 'test') return next();
|
||||||
|
res.json({status: 'unavailable'});
|
||||||
|
},
|
||||||
|
function(req, res, next){
|
||||||
|
if(!runner.report) return next();
|
||||||
|
res.json(testFilter(runner.report, req.query.type, req.query.n));
|
||||||
|
},
|
||||||
|
function(req, res){
|
||||||
|
runner.on('done', function(report){
|
||||||
|
process.nextTick(() => res.json(testFilter(runner.report, req.query.type, req.query.n)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.get('/_api/app-info', function(req, res) {
|
||||||
|
let hs = Object.keys(res._headers)
|
||||||
|
.filter(h => !h.match(/^access-control-\w+/));
|
||||||
|
let hObj = {};
|
||||||
|
hs.forEach(h => {hObj[h] = res._headers[h]});
|
||||||
|
delete res._headers['strict-transport-security'];
|
||||||
|
res.json({headers: hObj});
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function testFilter(tests, type, n) {
|
||||||
|
let out;
|
||||||
|
switch (type) {
|
||||||
|
case 'unit' :
|
||||||
|
out = tests.filter(t => t.context.match('Unit Tests'));
|
||||||
|
break;
|
||||||
|
case 'functional':
|
||||||
|
out = tests.filter(t => t.context.match('Functional Tests') && !t.title.match('#example'));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
out = tests;
|
||||||
|
}
|
||||||
|
if(n !== undefined) {
|
||||||
|
return out[n] || out;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
91
7-quality-assurance/3-library/server.js
Normal file
91
7-quality-assurance/3-library/server.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
|
const cors = require("cors");
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
const apiRoutes = require("./routes/api.js");
|
||||||
|
const fccTestingRoutes = require("./routes/fcctesting.js");
|
||||||
|
const runner = require("./test-runner");
|
||||||
|
|
||||||
|
db().catch((err) => console.log(err));
|
||||||
|
|
||||||
|
async function db() {
|
||||||
|
await mongoose.connect(process.env.MONGO_URI);
|
||||||
|
console.log("connected to DB");
|
||||||
|
await Books.create(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
_id: "63657911922d375e25ad1b85",
|
||||||
|
title: "The nya nya test book",
|
||||||
|
comments: ["mew", "meow", "NYA"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "63680748dad31302a987eaed",
|
||||||
|
title: "Comment modification testnya",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
(err, docs) => {
|
||||||
|
err ? console.log(err) : console.log(docs);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
startApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookSchema = new mongoose.Schema({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
comments: {
|
||||||
|
type: [String],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const Books = mongoose.model("Books", bookSchema);
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use("/public", express.static(process.cwd() + "/public"));
|
||||||
|
|
||||||
|
app.use(cors({ origin: "*" })); //USED FOR FCC TESTING PURPOSES ONLY!
|
||||||
|
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
|
|
||||||
|
//Index page (static HTML)
|
||||||
|
app.route("/").get(function (req, res) {
|
||||||
|
res.sendFile(process.cwd() + "/views/index.html");
|
||||||
|
});
|
||||||
|
|
||||||
|
//For FCC testing purposes
|
||||||
|
fccTestingRoutes(app);
|
||||||
|
|
||||||
|
//Routing for API
|
||||||
|
apiRoutes(app, Books);
|
||||||
|
|
||||||
|
//404 Not Found Middleware
|
||||||
|
app.use(function (req, res, next) {
|
||||||
|
res.status(404).type("text").send("Not Found");
|
||||||
|
});
|
||||||
|
|
||||||
|
//Start our server and tests!
|
||||||
|
function startApp() {
|
||||||
|
const listener = app.listen(process.env.PORT || 3000, function () {
|
||||||
|
console.log("Your app is listening on port " + listener.address().port);
|
||||||
|
if (process.env.NODE_ENV === "test") {
|
||||||
|
console.log("Running Tests...");
|
||||||
|
setTimeout(function () {
|
||||||
|
try {
|
||||||
|
runner.run();
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Tests are not valid:");
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
module.exports = app; //for unit/functional testing
|
106
7-quality-assurance/3-library/test-runner.js
Normal file
106
7-quality-assurance/3-library/test-runner.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* DO NOT EDIT THIS FILE
|
||||||
|
* For FCC testing purposes!
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const analyser = require('./assertion-analyser');
|
||||||
|
const EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
|
const Mocha = require('mocha'),
|
||||||
|
fs = require('fs'),
|
||||||
|
path = require('path');
|
||||||
|
|
||||||
|
const mocha = new Mocha();
|
||||||
|
const testDir = './tests'
|
||||||
|
|
||||||
|
|
||||||
|
// Add each .js file to the mocha instance
|
||||||
|
fs.readdirSync(testDir).filter(function(file){
|
||||||
|
// Only keep the .js files
|
||||||
|
return file.substr(-3) === '.js';
|
||||||
|
|
||||||
|
}).forEach(function(file){
|
||||||
|
mocha.addFile(
|
||||||
|
path.join(testDir, file)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let emitter = new EventEmitter();
|
||||||
|
emitter.run = function() {
|
||||||
|
|
||||||
|
let tests = [];
|
||||||
|
let context = "";
|
||||||
|
let separator = ' -> ';
|
||||||
|
// Run the tests.
|
||||||
|
try {
|
||||||
|
let runner = mocha.ui('tdd').run()
|
||||||
|
.on('test end', function(test) {
|
||||||
|
// remove comments
|
||||||
|
let body = test.body.replace(/\/\/.*\n|\/\*.*\*\//g, '');
|
||||||
|
// collapse spaces
|
||||||
|
body = body.replace(/\s+/g,' ');
|
||||||
|
let obj = {
|
||||||
|
title: test.title,
|
||||||
|
context: context.slice(0, -separator.length),
|
||||||
|
state: test.state,
|
||||||
|
// body: body,
|
||||||
|
assertions: analyser(body)
|
||||||
|
};
|
||||||
|
tests.push(obj);
|
||||||
|
})
|
||||||
|
.on('end', function() {
|
||||||
|
emitter.report = tests;
|
||||||
|
emitter.emit('done', tests)
|
||||||
|
})
|
||||||
|
.on('suite', function(s) {
|
||||||
|
context += (s.title + separator);
|
||||||
|
|
||||||
|
})
|
||||||
|
.on('suite end', function(s) {
|
||||||
|
context = context.slice(0, -(s.title.length + separator.length))
|
||||||
|
})
|
||||||
|
} catch(e) {
|
||||||
|
throw(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = emitter;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mocha.runner Events:
|
||||||
|
* can be used to build a better custom report
|
||||||
|
*
|
||||||
|
* - `start` execution started
|
||||||
|
* - `end` execution complete
|
||||||
|
* - `suite` (suite) test suite execution started
|
||||||
|
* - `suite end` (suite) all tests (and sub-suites) have finished
|
||||||
|
* - `test` (test) test execution started
|
||||||
|
* - `test end` (test) test completed
|
||||||
|
* - `hook` (hook) hook execution started
|
||||||
|
* - `hook end` (hook) hook complete
|
||||||
|
* - `pass` (test) test passed
|
||||||
|
* - `fail` (test, err) test failed
|
||||||
|
* - `pending` (test) test pending
|
||||||
|
*/
|
198
7-quality-assurance/3-library/tests/2_functional-tests.js
Normal file
198
7-quality-assurance/3-library/tests/2_functional-tests.js
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* FILL IN EACH FUNCTIONAL TEST BELOW COMPLETELY
|
||||||
|
* -----[Keep the tests in the same order!]-----
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const chaiHttp = require("chai-http");
|
||||||
|
const chai = require("chai");
|
||||||
|
const assert = chai.assert;
|
||||||
|
const server = require("../server");
|
||||||
|
|
||||||
|
chai.use(chaiHttp);
|
||||||
|
|
||||||
|
suite("Functional Tests", function () {
|
||||||
|
/*
|
||||||
|
* ----[EXAMPLE TEST]----
|
||||||
|
* Each test should completely test the response of the API end-point including response status code!
|
||||||
|
|
||||||
|
test("#example Test GET /api/books", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.get("/api/books")
|
||||||
|
.end(function (err, res) {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isArray(res.body, "response should be an array");
|
||||||
|
assert.property(
|
||||||
|
res.body[0],
|
||||||
|
"commentcount",
|
||||||
|
"Books in array should contain commentcount"
|
||||||
|
);
|
||||||
|
assert.property(
|
||||||
|
res.body[0],
|
||||||
|
"title",
|
||||||
|
"Books in array should contain title"
|
||||||
|
);
|
||||||
|
assert.property(
|
||||||
|
res.body[0],
|
||||||
|
"_id",
|
||||||
|
"Books in array should contain _id"
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
* ----[END of EXAMPLE TEST]----
|
||||||
|
*/
|
||||||
|
|
||||||
|
suite("Routing tests", function () {
|
||||||
|
suite(
|
||||||
|
"POST /api/books with title => create book object/expect book object",
|
||||||
|
function () {
|
||||||
|
test("Test POST /api/books with title", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.post("/api/books")
|
||||||
|
.send({ title: "Testbook" })
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.property(res.body, "_id");
|
||||||
|
assert.equal(res.body.title, "Testbook");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test POST /api/books with no title given", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.post("/api/books")
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isString(res.body);
|
||||||
|
assert.equal(res.body, "missing required field title");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
suite("GET /api/books => array of books", function () {
|
||||||
|
test("Test GET /api/books", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.get("/api/books")
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isArray(res.body);
|
||||||
|
assert.property(res.body[0], "commentcount");
|
||||||
|
assert.property(res.body[0], "title");
|
||||||
|
assert.property(res.body[0], "_id");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
suite("GET /api/books/[id] => book object with [id]", function () {
|
||||||
|
test("Test GET /api/books/[id] with id not in db", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.get("/api/books/nyanyanya")
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isString(res.body);
|
||||||
|
assert.equal(res.body, "no book exists");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test GET /api/books/[id] with valid id in db", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.get("/api/books/63657911922d375e25ad1b85")
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isObject(res.body);
|
||||||
|
assert.equal(res.body._id, "63657911922d375e25ad1b85");
|
||||||
|
assert.equal(res.body.title, "The nya nya test book");
|
||||||
|
assert.isArray(res.body.comments);
|
||||||
|
assert.equal(res.body.comments.length, 3);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
suite(
|
||||||
|
"POST /api/books/[id] => add comment/expect book object with id",
|
||||||
|
function () {
|
||||||
|
test("Test POST /api/books/[id] with comment", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.post("/api/books/63680748dad31302a987eaed")
|
||||||
|
.send({ comment: "MEEEEEEEEEEEEEEOW" })
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isObject(res.body);
|
||||||
|
assert.equal(res.body._id, "63680748dad31302a987eaed");
|
||||||
|
assert.equal(res.body.title, "Comment modification testnya");
|
||||||
|
assert.isArray(res.body.comments);
|
||||||
|
assert.equal(res.body.comments[0], "MEEEEEEEEEEEEEEOW");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test POST /api/books/[id] without comment field", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.post("/api/books/63680748dad31302a987eaed")
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isString(res.body);
|
||||||
|
assert.equal(res.body, "missing required field comment");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test POST /api/books/[id] with comment, id not in db", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.post("/api/books/nonexistingid")
|
||||||
|
.send({ comment: "bla" })
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isString(res.body);
|
||||||
|
assert.equal(res.body, "no book exists");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
suite("DELETE /api/books/[id] => delete book object id", function () {
|
||||||
|
test("Test DELETE /api/books/[id] with valid id in db", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.delete("/api/books/63657911922d375e25ad1b85")
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isString(res.body);
|
||||||
|
assert.equal(res.body, "delete successful");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test DELETE /api/books/[id] with id not in db", function (done) {
|
||||||
|
chai
|
||||||
|
.request(server)
|
||||||
|
.delete("/api/books/420")
|
||||||
|
.end((err, res) => {
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
assert.isString(res.body);
|
||||||
|
assert.equal(res.body, "no book exists");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
70
7-quality-assurance/3-library/views/index.html
vendored
Normal file
70
7-quality-assurance/3-library/views/index.html
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Personal Library</title>
|
||||||
|
<link rel="icon" type="image/png" href="https://cdn.freecodecamp.org/universal/favicons/favicon-16x16.png" />
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/public/style.css">
|
||||||
|
<style>
|
||||||
|
code {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Personal Library</h1>
|
||||||
|
</header>
|
||||||
|
<br>
|
||||||
|
<div id ='sampleposting'>
|
||||||
|
<h2 style="text-align: left">Test API responses</h2>
|
||||||
|
<form action="/api/books" method="post" class="border">
|
||||||
|
<h4>Test post to /api/books</h4>
|
||||||
|
<label for="title" >Book Title</label>
|
||||||
|
<input type="text" id="title" name="title" value=""><br>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
<form action="" method="post" id="commentTest" class="border">
|
||||||
|
<h4>Test post to /api/books/{bookid}</h4>
|
||||||
|
<label for="idinputtest">BookId to comment on</label>
|
||||||
|
<input type="text" name="id" value="" id="idinputtest"><br>
|
||||||
|
<label for="comment">Comment</label>
|
||||||
|
<input type="text" id="comment" name="comment" value=""><br>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<hr style='margin: 50px'>
|
||||||
|
<div id='sampleui'>
|
||||||
|
<h2 style="text-align: left">Sample Front-End</h2>
|
||||||
|
<form id="newBookForm" class="border">
|
||||||
|
<label for="bookTitleToAdd">New Book Title</label>
|
||||||
|
<input type="text" id="bookTitleToAdd" name="title" placeholder="Moby Dick" style="width: 295px">
|
||||||
|
<button type="submit" value="Submit" id="newBook">Submit New Book!</button>
|
||||||
|
</form>
|
||||||
|
<div id='display'></div>
|
||||||
|
<div id='bookDetail' class='border'>
|
||||||
|
<p id='detailTitle'>Select a book to see it's details and comments</p>
|
||||||
|
<ol id='detailComments'></ol>
|
||||||
|
</div>
|
||||||
|
<button id='deleteAllBooks'>Delete all books...</button>
|
||||||
|
</div>
|
||||||
|
<hr style='margin: 50px'>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-2.2.1.min.js"
|
||||||
|
integrity="sha256-gvQgAFzTH6trSrAWoH1iPo9Xc96QxSZ3feW6kem+O00="
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="/public/client.js"></script>
|
||||||
|
<script>
|
||||||
|
/*
|
||||||
|
* For #sampleposting to update form action url to test inputs book id
|
||||||
|
*/
|
||||||
|
$(function() {
|
||||||
|
$('#commentTest').submit(function(){
|
||||||
|
let id = $('#idinputtest').val();
|
||||||
|
$(this).attr('action', "/api/books/" + id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Reference in a new issue