diff --git a/lab-nathan/.eslintignore b/lab-nathan/.eslintignore new file mode 100644 index 0000000..c6abca4 --- /dev/null +++ b/lab-nathan/.eslintignore @@ -0,0 +1,6 @@ +**/node_modules/* +**/vendor/* +**/*.min.js +**/coverage/* +**/build/* +**/assets/* diff --git a/lab-nathan/.eslintrc b/lab-nathan/.eslintrc new file mode 100644 index 0000000..c8dfef7 --- /dev/null +++ b/lab-nathan/.eslintrc @@ -0,0 +1,23 @@ +{ + "rules": { + "no-console": "off", + "indent": [ "error", 2 ], + "quotes": [ "error", "single" ], + "semi": ["error", "always"], + "linebreak-style": [ "error", "unix" ] + }, + "env": { + "es6": true, + "node": true, + "mocha": true, + "jasmine": true + }, + "parserOptions": { + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true, + "impliedStrict": true + } + }, + "extends": "eslint:recommended" +} diff --git a/lab-nathan/.gitignore b/lab-nathan/.gitignore new file mode 100644 index 0000000..115b88e --- /dev/null +++ b/lab-nathan/.gitignore @@ -0,0 +1,138 @@ +# Created by https://www.gitignore.io/api/osx,vim,node,macos,windows + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### OSX ### + +# Icon must end with two \r + +# Thumbnails + +# Files that might appear in the root of a volume + +# Directories potentially created on remote AFP share + +### Vim ### +# swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/osx,vim,node,macos,windows + +data diff --git a/lab-nathan/README.md b/lab-nathan/README.md new file mode 100644 index 0000000..8634d94 --- /dev/null +++ b/lab-nathan/README.md @@ -0,0 +1,53 @@ +# Generic REST API with Persistence + +This project contains a basic REST API featuring a router and file-system persistance. You can access the API by making an HTTP request to one of the endpoints listed below. + +## Resources +The following resources are available for storage and retrieval: + +Note: ids are automatically generated. + +```js +function Contact() { + id: '6c5036a6-913d-4475-91b0-084e4115e61b' + firstName: 'Abraham' + lastName: 'Lincoln' + email: 'abraham@lincoln.com' + phone: '1-800-ABRAHAM' +} +``` +```js +function Note() { + id: '6c5036a6-913d-4475-91b0-084e4115e61b' + name: 'TPS Reports' + content: 'Did you get the memo?' +} +``` + +## Contact Endpoints + +### GET `/api/contact?id=` +Gets a JSON representation of a Contact with the specified id. + +### PUT `/api/contact?id=` +Updates a Contact with the specified id. The body of the request should be a serialized JSON object containing the updated property values. + +### POST `/api/contact` +Creates a Contact object. The body of the request should be a serialized JSON object. \containing the Contact's property values. + +### DELETE `/api/contact?id=` +Deletes a Contact object with the specified id. + +## Note Endpoints + +### GET `/api/note?id=` +Gets a JSON representation of a Contact with the specified id. + +### PUT `/api/note` +Updates a Note with the specified id. The body of the request should be a serialized JSON object containing the updated property values. + +### POST `/api/note?id=` +Creates a Note object. The body of the request should be a serialized JSON object. \containing the Note's property values. + +### DELETE `/api/note?id=` +Deletes a Contact with the specified id. diff --git a/lab-nathan/lib/fs-promises.js b/lab-nathan/lib/fs-promises.js new file mode 100644 index 0000000..21662a9 --- /dev/null +++ b/lab-nathan/lib/fs-promises.js @@ -0,0 +1,68 @@ +'use strict'; + +const fs = require('fs'); + +let fsPromises = {}; + +module.exports = fsPromises; + +fsPromises.readDirectory = function(directory) { + return new Promise((resolve, reject) => { + fs.readdir(directory, function(error, files) { + if (error) { + return reject(error); + } + + resolve(files); + }); + }); +}; + +fsPromises.createDirectory = function(directory) { + return new Promise((resolve, reject) => { + fs.mkdir(directory, function(error) { + if (error && error.code !== 'EEXIST') { + return reject(error); + } + + resolve(); + }); + }); +}; + +fsPromises.readFile = function(filePath) { + return new Promise((resolve, reject) => { + fs.readFile(filePath, function(error, buffer) { + if (error) { + return reject(error); + } + + resolve(buffer); + }); + }); +}; + +fsPromises.writeFile = function(filePath, item) { + return new Promise((resolve, reject) => { + fs.writeFile(filePath, item, function(error) { + if (error) { + return reject(error); + } + + resolve(); + }); + }); +}; + +fsPromises.deleteFile = function(path) { + return new Promise((resolve, reject) => { + fs.unlink(path, function(error) { + if (error) { + return reject(error); + } + + resolve(); + }); + }); +}; + diff --git a/lab-nathan/lib/parse-json.js b/lab-nathan/lib/parse-json.js new file mode 100644 index 0000000..d492eff --- /dev/null +++ b/lab-nathan/lib/parse-json.js @@ -0,0 +1,37 @@ +'use strict'; + +module.exports = parseJson; + +function parseJson(request) { + return new Promise((resolve, reject) => { + if (request.method !== 'POST' && request.method !== 'PUT') { + resolve(); + return; + } + + let body = ''; + + request.on('data', function(buffer) { + body += buffer.toString(); + }); + + request.on('end', () => { + if (!body) { + resolve(request); + return; + } + + try { + request.body = JSON.parse(body); + resolve(request); + } + catch (error) { + reject(error); + } + }); + + request.on('error', function(error) { + reject(error); + }); + }); +} diff --git a/lab-nathan/lib/parse-url.js b/lab-nathan/lib/parse-url.js new file mode 100644 index 0000000..c530124 --- /dev/null +++ b/lab-nathan/lib/parse-url.js @@ -0,0 +1,13 @@ +'use strict'; + +const url = require('url'); +const queryString = require('querystring'); + +module.exports = parseUrl; + +function parseUrl(request) { + request.url = url.parse(request.url); + request.url.query = queryString.parse(request.url.query); + + return Promise.resolve(request); +} \ No newline at end of file diff --git a/lab-nathan/lib/response-extensions.js b/lab-nathan/lib/response-extensions.js new file mode 100644 index 0000000..d59e8da --- /dev/null +++ b/lab-nathan/lib/response-extensions.js @@ -0,0 +1,30 @@ +'use strict'; + +const http = require('http'); + +http.ServerResponse.prototype.sendJson = function(statusCode, item) { + this.send(statusCode, JSON.stringify(item), 'application/json'); +}; + +http.ServerResponse.prototype.sendText = function(statusCode, message) { + this.send(statusCode, message); +}; + +http.ServerResponse.prototype.send = function(statusCode, message, contentType = 'text/plain') { + if (contentType) { + let headers = { + 'Content-Type': contentType + }; + + this.writeHead(statusCode, headers); + } + else { + this.writeHead(statusCode); + } + + if (message) { + this.write(message); + } + + this.end(); +}; \ No newline at end of file diff --git a/lab-nathan/lib/router.js b/lab-nathan/lib/router.js new file mode 100644 index 0000000..9d6b567 --- /dev/null +++ b/lab-nathan/lib/router.js @@ -0,0 +1,59 @@ +'use strict'; + +require('./response-extensions.js'); + +module.exports = Router; + +function Router() { + this.routes = { + GET: {}, + PUT: {}, + POST: {}, + DELETE: {} + }; + + this.middleware = []; +} + +Router.prototype.handleGet = function(endpoint, callback) { + this.routes.GET[endpoint] = callback; +}; + +Router.prototype.handlePut = function(endpoint, callback) { + this.routes.PUT[endpoint] = callback; +}; + +Router.prototype.handlePost = function(endpoint, callback) { + this.routes.POST[endpoint] = callback; +}; + +Router.prototype.handleDelete = function(endpoint, callback) { + this.routes.DELETE[endpoint] = callback; +}; + +Router.prototype.addMiddleware = function(promise) { + this.middleware.push(promise); +}; + +Router.prototype.route = function(request, response) { + Promise.all(this.middleware.map(m => m(request))) + .then(() => { + let endpoint = request.url.pathname; + let method = request.method; + + let callback = this.routes[method][endpoint]; + + if (callback) { + callback(request, response); + return Promise.resolve(); + } + + response.sendText(404, 'Route not found.'); + return Promise.resolve(); + }) + .catch(function(error) { + console.error(error); + response.sendText(400, 'Bad request.'); + return Promise.resolve(); + }); +}; \ No newline at end of file diff --git a/lab-nathan/lib/storage.js b/lab-nathan/lib/storage.js new file mode 100644 index 0000000..427edf3 --- /dev/null +++ b/lab-nathan/lib/storage.js @@ -0,0 +1,65 @@ +'use strict'; + +const fsPromises = require('./fs-promises.js'); + +let storage = {}; + +module.exports = storage; + +storage.add = function(categoryName, item) { + if (!categoryName) { + return Promise.reject(new Error('expected schema name')); + } + + if (!item) { + return Promise.reject(new Error('expected item')); + } + + return fsPromises.createDirectory(`${__dirname}/../data`) + .then(fsPromises.createDirectory(`${__dirname}/../data/${categoryName}`)) + .then(fsPromises.writeFile(`${__dirname}/../data/${categoryName}/${item.id}.json`, JSON.stringify(item))) + .then(Promise.resolve(item)); +}; + +storage.get = function(categoryName, id) { + if (!categoryName) { + return Promise.reject(new Error('expected schema name')); + } + + if (!id) { + return Promise.reject(new Error('expected id')); + } + + return fsPromises.readFile(`${__dirname}/../data/${categoryName}/${id}.json`) + .then(buffer => { + try { + let jsonString = buffer.toString(); + let item = JSON.parse(jsonString); + return Promise.resolve(item); + } + catch(error) { + return Promise.reject(error); + } + }); +}; + +storage.update = function(categoryName, id, data) { + return storage.get(categoryName, id) + .then(item => { + for (let propertyName in data) { + if (item[propertyName]) { + item[propertyName] = data[propertyName]; + } + } + + return Promise.resolve(item); + }); +}; + +storage.remove = function(categoryName, id) { + return fsPromises.deleteFile(`${__dirname}/../data/${categoryName}/${id}.json`); +}; + +storage.getIds = function(categoryName) { + return fsPromises.readDirectory(`${__dirname}/../data/${categoryName}`); +}; \ No newline at end of file diff --git a/lab-nathan/model/contact.js b/lab-nathan/model/contact.js new file mode 100644 index 0000000..4d7cfe0 --- /dev/null +++ b/lab-nathan/model/contact.js @@ -0,0 +1,29 @@ +'use strict'; + +const uuidv4 = require('uuid/v4'); + +module.exports = Contact; + +function Contact(firstName, lastName, email, phone) { + if (!firstName) { + throw new Error('No first name provided.'); + } + + if (!lastName) { + throw new Error('No last name provided.'); + } + + if (!email) { + throw new Error('No email provided.'); + } + + if (!phone) { + throw new Error('No phone provided.'); + } + + this.id = uuidv4(); + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.phone = phone; +} \ No newline at end of file diff --git a/lab-nathan/model/note.js b/lab-nathan/model/note.js new file mode 100644 index 0000000..36674f5 --- /dev/null +++ b/lab-nathan/model/note.js @@ -0,0 +1,19 @@ +'use strict'; + +const uuidv4 = require('uuid/v4'); + +module.exports = Note; + +function Note(name, content) { + if (!name) { + throw new Error('No name provided.'); + } + + if (!content) { + throw new Error('No content provided.'); + } + + this.id = uuidv4(); + this.name = name; + this.content = content; +} \ No newline at end of file diff --git a/lab-nathan/package.json b/lab-nathan/package.json new file mode 100644 index 0000000..f336ff2 --- /dev/null +++ b/lab-nathan/package.json @@ -0,0 +1,22 @@ +{ + "name": "lab-nathan", + "version": "1.0.0", + "description": "", + "main": "server.js", + "scripts": { + "test": "mocha", + "start": "node server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "uuid": "^3.1.0" + }, + "devDependencies": { + "bluebird": "^3.5.0", + "chai": "^4.1.0", + "mocha": "^3.4.2", + "superagent": "^3.5.2" + } +} diff --git a/lab-nathan/routes/contact-routes.js b/lab-nathan/routes/contact-routes.js new file mode 100644 index 0000000..a5385fc --- /dev/null +++ b/lab-nathan/routes/contact-routes.js @@ -0,0 +1,96 @@ +'use strict'; + +require('../lib/response-extensions.js'); +const storage = require('../lib/storage.js'); +const Contact = require('../model/contact.js'); + +module.exports = contactRoutes; + +function contactRoutes(router) { + router.handlePost('/api/contact', function(request, response) { + let contact; + + try { + contact = new Contact(request.body.firstName, request.body.lastName, request.body.email, request.body.phone); + } + catch(error) { + response.sendText(400, 'Posted contact was invalid.'); + return; + } + + storage.add('contacts', contact) + .then(() => { + response.sendJson(200, contact); + return Promise.resolve(); + }) + .catch(() => { + response.sendText(400, 'Posted contact was invalid.'); + return Promise.resolve(); + }); + }); + + router.handleGet('/api/contact', function(request, response) { + let id = request.url.query.id; + + if (!id) { + storage.getIds('contacts') + .then(files => { + files = files.map(file => { + let fileParts = file.split('.'); + fileParts.pop(); + return fileParts.join('.'); + }); + + response.sendJson(200, files); + return Promise.resolve(); + }) + .catch(() => { + response.sendJson(500, 'Could not get contact ids.'); + return Promise.resolve(); + }); + + return; + } + + storage.get('contacts', id) + .then(contact => { + response.sendJson(200, contact); + return Promise.resolve(); + }) + .catch(() => { + response.sendText(404, 'Contact not found.'); + return Promise.resolve(); + }); + }); + + router.handlePut('/api/contact', function(request, response) { + storage.update('contacts', request.url.query.id, request.body) + .then(contact => { + response.sendJson(200, contact); + return Promise.resolve(); + }) + .catch(() => { + response.sendText(400, 'Bad request.'); + return Promise.resolve(); + }); + }); + + router.handleDelete('/api/contact', function(request, response) { + let id = request.url.query.id; + + if (!id) { + response.sendText(200, 'No id provided.'); + return; + } + + storage.remove('contacts', id) + .then(() => { + response.send(204, null, null); + return Promise.resolve(); + }) + .catch(() => { + response.sendText(404, 'Contact not found.'); + return Promise.resolve(); + }); + }); +} \ No newline at end of file diff --git a/lab-nathan/routes/note-routes.js b/lab-nathan/routes/note-routes.js new file mode 100644 index 0000000..d579d2a --- /dev/null +++ b/lab-nathan/routes/note-routes.js @@ -0,0 +1,97 @@ +'use strict'; + +require('../lib/response-extensions.js'); +const storage = require('../lib/storage.js'); +const Note = require('../model/note.js'); + +module.exports = noteRoutes; + +function noteRoutes(router) { + router.handlePost('/api/note', function(request, response) { + let note; + + try { + note = new Note(request.body.name, request.body.content); + } + catch(error) { + response.sendText(400, 'Posted note was invalid.'); + return; + } + + storage.add('notes', note) + .then(() => { + response.sendJson(200, note); + return Promise.resolve(); + }) + .catch(error => { + console.error(error); + response.sendText(400, 'Posted note was invalid.'); + return Promise.resolve(); + }); + }); + + router.handleGet('/api/note', function(request, response) { + let id = request.url.query.id; + + if (!id) { + storage.getIds('notes') + .then(files => { + files = files.map(file => { + let fileParts = file.split('.'); + fileParts.pop(); + return fileParts.join('.'); + }); + + response.sendJson(200, files); + return Promise.resolve(); + }) + .catch(() => { + response.sendJson(500, 'Could not get note ids.'); + return Promise.resolve(); + }); + + return; + } + + storage.get('notes', id) + .then(note => { + response.sendJson(200, note); + return Promise.resolve(); + }) + .catch(() => { + response.sendText(404, 'Note not found.'); + return Promise.resolve(); + }); + }); + + router.handlePut('/api/note', function(request, response) { + storage.update('notes', request.url.query.id, request.body) + .then(note => { + response.sendJson(200, note); + return Promise.resolve(); + }) + .catch(() => { + response.sendText(400, 'Bad request.'); + return Promise.resolve(); + }); + }); + + router.handleDelete('/api/note', function(request, response) { + let id = request.url.query.id; + + if (!id) { + response.sendText(200, 'No id provided.'); + return; + } + + storage.remove('notes', id) + .then(() => { + response.send(204, null, null); + return Promise.resolve(); + }) + .catch(() => { + response.sendText(404, 'Note not found.'); + return Promise.resolve(); + }); + }); +} \ No newline at end of file diff --git a/lab-nathan/server.js b/lab-nathan/server.js new file mode 100644 index 0000000..7feebed --- /dev/null +++ b/lab-nathan/server.js @@ -0,0 +1,22 @@ +'use strict'; + +const http = require('http'); +const Router = require('./lib/router.js'); +const parseJson = require('./lib/parse-json.js'); +const parseUrl = require('./lib/parse-url.js'); +const noteRoutes = require('./routes/note-routes.js'); +const contactRoutes = require('./routes/contact-routes.js'); + +const router = new Router(); + +router.addMiddleware(parseJson); +router.addMiddleware(parseUrl); + +noteRoutes(router); +contactRoutes(router); + +const PORT = process.env.PORT || 3000; +const server = http.createServer(router.route.bind(router)); +server.listen(PORT, function() { + console.log(`Listening on port ${PORT}.`); +}); \ No newline at end of file diff --git a/lab-nathan/test/contact-route-test.js b/lab-nathan/test/contact-route-test.js new file mode 100644 index 0000000..7f65ff9 --- /dev/null +++ b/lab-nathan/test/contact-route-test.js @@ -0,0 +1,160 @@ +'use strict'; + +const request = require('superagent'); +const expect = require('chai').expect; + +require('../server.js'); + +describe('Contact Routes', function() { + let contact = null; + + describe('POST: /api/contact', function() { + it('should return a contact', function(done) { + request.post('localhost:8000/api/contact') + .send({ + firstName: 'bob', + lastName: 'vila', + email: 'bob@vila.com', + phone: '1-800-BOB-VILA' + }) + .end(function(err, response) { + if (err) { + return done(err); + } + + expect(response.status).to.equal(200); + expect(response.body.firstName).to.equal('bob'); + expect(response.body.lastName).to.equal('vila'); + expect(response.body.email).to.equal('bob@vila.com'); + expect(response.body.phone).to.equal('1-800-BOB-VILA'); + + contact = response.body; + + done(); + }); + }); + + it('should respond with bad request if no request body was provided or the body was invalid', function(done) { + request.post('localhost:8000/api/contact') + .send({ + gobble: 'gobble' + }) + .end(function(error) { + expect(error.status).to.equal(400); + expect(error.response.text).to.equal('Posted contact was invalid.'); + + done(); + }); + }); + + + }); + + describe('GET: /api/contact', function() { + it('should return a contact', function(done) { + request.get(`localhost:8000/api/contact?id=${contact.id}`) + .end(function(err, response){ + if (err) { + return done(err); + } + + expect(response.status).to.equal(200); + expect(response.body.firstName).to.equal('bob'); + expect(response.body.lastName).to.equal('vila'); + expect(response.body.email).to.equal('bob@vila.com'); + expect(response.body.phone).to.equal('1-800-BOB-VILA'); + + done(); + }); + }); + + it('should return a status code of 404 for routes that have not been registered', function(done) { + request.get('localhost:8000/api/dinosaurs') + .end(function(error) { + expect(error.status).to.equal(404); + expect(error.response.text).to.equal('Route not found.'); + + done(); + }); + }); + + it('should respond with not found for valid requests made with an id that was not found', function(done) { + request.get('localhost:8000/api/contact?id=100') + .end(function(error) { + expect(error.status).to.equal(404); + expect(error.response.text).to.equal('Contact not found.'); + + done(); + }); + }); + + it('it should respond with an array of all ids if no id was provided in the request', function(done) { + request.get('localhost:8000/api/contact') + .end(function(error, response) { + expect(response.body).to.deep.equal([ `${contact.id}` ]); + + done(); + }); + }); + }); + + describe('PUT: /api/contact', function() { + it('should return an updated contact', function(done) { + request.put(`localhost:8000/api/contact?id=${contact.id}`) + .send({ + firstName: 'abraham', + lastName: 'lincoln', + email: 'abraham@lincoln.com', + phone: '1-800-ABRAHAM' + }) + .end(function(err, response){ + if (err) { + return done(err); + } + + expect(response.status).to.equal(200); + expect(response.body.firstName).to.equal('abraham'); + expect(response.body.lastName).to.equal('lincoln'); + expect(response.body.email).to.equal('abraham@lincoln.com'); + expect(response.body.phone).to.equal('1-800-ABRAHAM'); + + done(); + }); + }); + + it('should return a status code of 404 for routes that have not been registered', function(done) { + request.put('localhost:8000/api/dinosaurs') + .end(function(error) { + expect(error.status).to.equal(404); + expect(error.response.text).to.equal('Route not found.'); + + done(); + }); + }); + + it('should respond with bad request for invalid requests', function(done) { + request.put('localhost:8000/api/contact?id=100') + .end(function(error) { + expect(error.status).to.equal(400); + expect(error.response.text).to.equal('Bad request.'); + + done(); + }); + }); + }); + + describe('DELETE: /api/contact', function() { + it('should delete a contact', function(done) { + request.delete(`localhost:8000/api/contact?id=${contact.id}`) + .end(function(error, response){ + if (error) { + return done(error); + } + + expect(response.status).to.equal(204); + + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/lab-nathan/test/note-route-test.js b/lab-nathan/test/note-route-test.js new file mode 100644 index 0000000..819222e --- /dev/null +++ b/lab-nathan/test/note-route-test.js @@ -0,0 +1,151 @@ +'use strict'; + +const request = require('superagent'); +const expect = require('chai').expect; + +require('../server.js'); + +describe('Note Routes', function() { + let note = null; + + describe('POST: /api/note', function() { + it('should return a note', function(done) { + request.post('localhost:8000/api/note') + .send({ + name: 'test name', + content: 'test content' + }) + .end(function(err, response) { + if (err) { + return done(err); + } + + expect(response.status).to.equal(200); + expect(response.body.name).to.equal('test name'); + expect(response.body.content).to.equal('test content'); + + note = response.body; + + done(); + }); + }); + + it('should respond with bad request if no request body was provided or the body was invalid', function(done) { + request.post('localhost:8000/api/note') + .send({ + eman: 'eman tset', + tnetnoc: 'tnetnoc tset' + }) + .end(function(error) { + expect(error.status).to.equal(400); + expect(error.response.text).to.equal('Posted note was invalid.'); + + done(); + }); + }); + + + }); + + describe('GET: /api/note', function() { + it('should return a note', function(done) { + request.get(`localhost:8000/api/note?id=${note.id}`) + .end(function(err, response){ + if (err) { + return done(err); + } + + expect(response.status).to.equal(200); + expect(response.body.name).to.equal('test name'); + expect(response.body.content).to.equal('test content'); + + done(); + }); + }); + + it('should return a status code of 404 for routes that have not been registered', function(done) { + request.get('localhost:8000/api/dinosaurs') + .end(function(error) { + expect(error.status).to.equal(404); + expect(error.response.text).to.equal('Route not found.'); + + done(); + }); + }); + + it('should respond with not found for valid requests made with an id that was not found', function(done) { + request.get('localhost:8000/api/note?id=100') + .end(function(error) { + expect(error.status).to.equal(404); + expect(error.response.text).to.equal('Note not found.'); + + done(); + }); + }); + + it('it should respond with an array of all ids if no id was provided in the request', function(done) { + request.get('localhost:8000/api/note') + .end(function(error, response) { + expect(response.body).to.deep.equal([ `${note.id}` ]); + + done(); + }); + }); + }); + + describe('PUT: /api/note', function() { + it('should return an updated note', function(done) { + request.put(`localhost:8000/api/note?id=${note.id}`) + .send({ + name: 'abraham', + content: 'abraham lincoln was a president.', + }) + .end(function(err, response){ + if (err) { + return done(err); + } + + expect(response.status).to.equal(200); + expect(response.body.name).to.equal('abraham'); + expect(response.body.content).to.equal('abraham lincoln was a president.'); + + done(); + }); + }); + + it('should return a status code of 404 for routes that have not been registered', function(done) { + request.put('localhost:8000/api/dinosaurs') + .end(function(error) { + expect(error.status).to.equal(404); + expect(error.response.text).to.equal('Route not found.'); + + done(); + }); + }); + + it('should respond with bad request for invalid requests', function(done) { + request.put('localhost:8000/api/note?id=100') + .end(function(error) { + expect(error.status).to.equal(400); + expect(error.response.text).to.equal('Bad request.'); + + done(); + }); + }); + }); + + describe('DELETE: /api/note', function() { + it('should delete a note', function(done) { + request.delete(`localhost:8000/api/note?id=${note.id}`) + .end(function(error, response){ + if (error) { + return done(error); + } + + expect(response.status).to.equal(204); + + done(); + }); + }); + }); +}); \ No newline at end of file