From 2e72b624aea225474a69fb038f86a1ce2a05ed4e Mon Sep 17 00:00:00 2001 From: radenska Date: Tue, 28 Feb 2017 12:51:37 -0800 Subject: [PATCH 1/3] basic file structure in place, dependencies installed, package json created and modified --- lab-yana/.eslintrc | 21 ++++++ lab-yana/.gitignore | 118 ++++++++++++++++++++++++++++++ lab-yana/README.md | 44 +++++++++++ lab-yana/gulpfile.js | 20 +++++ lab-yana/lib/cors-middleware.js | 0 lab-yana/lib/error-middleware.js | 0 lab-yana/lib/storage.js | 0 lab-yana/model/blog.js | 0 lab-yana/package.json | 30 ++++++++ lab-yana/route/blog-router.js | 0 lab-yana/server.js | 0 lab-yana/test/blog-routes-test.js | 0 12 files changed, 233 insertions(+) create mode 100644 lab-yana/.eslintrc create mode 100644 lab-yana/.gitignore create mode 100644 lab-yana/README.md create mode 100644 lab-yana/gulpfile.js create mode 100644 lab-yana/lib/cors-middleware.js create mode 100644 lab-yana/lib/error-middleware.js create mode 100644 lab-yana/lib/storage.js create mode 100644 lab-yana/model/blog.js create mode 100644 lab-yana/package.json create mode 100644 lab-yana/route/blog-router.js create mode 100644 lab-yana/server.js create mode 100644 lab-yana/test/blog-routes-test.js diff --git a/lab-yana/.eslintrc b/lab-yana/.eslintrc new file mode 100644 index 0000000..8dc6807 --- /dev/null +++ b/lab-yana/.eslintrc @@ -0,0 +1,21 @@ +{ + "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 + }, + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true, + "impliedStrict": true + }, + "extends": "eslint:recommended" +} diff --git a/lab-yana/.gitignore b/lab-yana/.gitignore new file mode 100644 index 0000000..401a48b --- /dev/null +++ b/lab-yana/.gitignore @@ -0,0 +1,118 @@ +# Created by https://www.gitignore.io/api/node,vim,osx,macos,linux + +*node_modules +*data/blog + +### Node ### +# Logs +logs +*.log +npm-debug.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 + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# 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 + + + +### 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 + + +### OSX ### +*.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 + + +### macOS ### +# Icon must end with two \r +# Thumbnails +# Files that might appear in the root of a volume +# Directories potentially created on remote AFP share + + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# End of https://www.gitignore.io/api/node,vim,osx,macos,linux diff --git a/lab-yana/README.md b/lab-yana/README.md new file mode 100644 index 0000000..ad37df8 --- /dev/null +++ b/lab-yana/README.md @@ -0,0 +1,44 @@ +### My Fisher Price First Express API for Blogs!!!! + +This is a single resource API using express middleware to make creating a server and routing requests much more efficient and painless. It allows you to upload a blog entry, look at stored blog entries, see a list of available blog entries, and delete a blog entry. + +### Directions for Use + +* make sure you have [node.js and npm installed](https://docs.npmjs.com/getting-started/installing-node), then in a terminal window, clone the code: + ``` + $ git clone https://github.com/radenska/11-single_resource_express_api.git + ``` + +* run npm i: + ``` + $ npm i + ``` +* start the server! + ``` + $ node server.js + ``` +* in a new terminal window, do the stuff! Here are your options: + - look at a blog entry: + ``` + $ http :3003/api/blog/blogID + ``` + - upload a blog entry: + ``` + $ http POST :3003/api/blog name="blog entry name" content="blog entry contents" + ``` + - look at a list of IDs of available blog entries (so you know how to look up a specific one!): + ``` + $ http :3003/api/blog + ``` + - delete a blog entry: + ``` + $ http DELETE :3003/api/blog/blogID + ``` + - update a blog entry (if you don't include the id, a new entry will be created instead): + ``` + $ http POST :3003/api/blog id="blogID" name="new name" content="new content" + ``` + +That's it! + +##### Created by Yana Radenska diff --git a/lab-yana/gulpfile.js b/lab-yana/gulpfile.js new file mode 100644 index 0000000..9da355a --- /dev/null +++ b/lab-yana/gulpfile.js @@ -0,0 +1,20 @@ +'use strict'; + +const gulp = require('gulp'); +const eslint = require('gulp-eslint'); +const mocha = require('gulp-mocha'); + +gulp.task('test', function() { + gulp.src('./test/*-test.js', { read: false }) + .pipe(mocha({ reporter: 'spec' })); +}); + +gulp.task('lint', function() { + return gulp.src(['./**/*.js', '!node_modules/**']).pipe(eslint()).pipe(eslint.format()).pipe(eslint.failAfterError()); +}); + +gulp.task('dev', function() { + gulp.watch(['**/*.js', '!node_modules/**'], ['lint', 'test']); +}); + +gulp.task('default', ['dev']); diff --git a/lab-yana/lib/cors-middleware.js b/lab-yana/lib/cors-middleware.js new file mode 100644 index 0000000..e69de29 diff --git a/lab-yana/lib/error-middleware.js b/lab-yana/lib/error-middleware.js new file mode 100644 index 0000000..e69de29 diff --git a/lab-yana/lib/storage.js b/lab-yana/lib/storage.js new file mode 100644 index 0000000..e69de29 diff --git a/lab-yana/model/blog.js b/lab-yana/model/blog.js new file mode 100644 index 0000000..e69de29 diff --git a/lab-yana/package.json b/lab-yana/package.json new file mode 100644 index 0000000..7a8ee0b --- /dev/null +++ b/lab-yana/package.json @@ -0,0 +1,30 @@ +{ + "name": "lab-yana", + "version": "1.0.0", + "description": "", + "main": "gulpfule.js", + "scripts": { + "test": "DEBUG='blog*' mocha", + "start": "DEBUG='blog*' node server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "chai": "^3.5.0", + "gulp": "^3.9.1", + "gulp-eslint": "^3.0.1", + "gulp-mocha": "^4.0.1", + "mocha": "^3.2.0", + "superagent": "^3.5.0" + }, + "dependencies": { + "bluebird": "^3.4.7", + "body-parser": "^1.16.1", + "debug": "^2.6.1", + "express": "^4.14.1", + "mkdirp": "^0.5.1", + "morgan": "^1.8.1", + "node-uuid": "^1.4.7" + } +} diff --git a/lab-yana/route/blog-router.js b/lab-yana/route/blog-router.js new file mode 100644 index 0000000..e69de29 diff --git a/lab-yana/server.js b/lab-yana/server.js new file mode 100644 index 0000000..e69de29 diff --git a/lab-yana/test/blog-routes-test.js b/lab-yana/test/blog-routes-test.js new file mode 100644 index 0000000..e69de29 From 4c6a724e777979212d619bbae452b6c06644ae28 Mon Sep 17 00:00:00 2001 From: radenska Date: Tue, 28 Feb 2017 14:55:40 -0800 Subject: [PATCH 2/3] wrote code for everything but README and test, not error checked --- lab-yana/lib/cors-middleware.js | 7 +++++ lab-yana/lib/error-middleware.js | 18 +++++++++++++ lab-yana/lib/storage.js | 45 ++++++++++++++++++++++++++++++++ lab-yana/model/blog.js | 43 ++++++++++++++++++++++++++++++ lab-yana/route/blog-router.js | 41 +++++++++++++++++++++++++++++ lab-yana/server.js | 20 ++++++++++++++ 6 files changed, 174 insertions(+) diff --git a/lab-yana/lib/cors-middleware.js b/lab-yana/lib/cors-middleware.js index e69de29..37c8062 100644 --- a/lab-yana/lib/cors-middleware.js +++ b/lab-yana/lib/cors-middleware.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function(req, res, next) { + res.append('Access-Control-Allow-Origin', '*'); + res.append('Access-Control-Allow-Headers', '#'); + next(); +} diff --git a/lab-yana/lib/error-middleware.js b/lab-yana/lib/error-middleware.js index e69de29..311f061 100644 --- a/lab-yana/lib/error-middleware.js +++ b/lab-yana/lib/error-middleware.js @@ -0,0 +1,18 @@ +'use strict'; + +const debug = require('debug')('blog:error-middleware'); +const createError = require('http-errors'); + +module.exports = function(err, req, res, next) { + console.error(err.message); + if (err.status) { + debug('user error'); + res.status(err.status).send(err.name); + next(); + return; + } + debug('server error'); + err = createError(500, err.message); + res.status(err.status).send(err.name); + next(); +}; diff --git a/lab-yana/lib/storage.js b/lab-yana/lib/storage.js index e69de29..dcb7273 100644 --- a/lab-yana/lib/storage.js +++ b/lab-yana/lib/storage.js @@ -0,0 +1,45 @@ +'use strict'; + +const debug = require('debug'); +const createError = require('http-error'); +const Promise = require('bluebird'); +const fs = require('fs').promisifyAll( {suffix: 'Prom'} ); + +module.exports = exports = {} + +exports.deleteItem = function(schemaName, id) { + debug('deleteItem'); + if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); + if (!id) return Promise.reject(createError(400, 'expected id')); + + return fs.unlinkProm(`../data/${schemaName}/${id}.json`) + .catch(err => { return Promise.reject(createError(404, err.message)); }); +}; + +exports.fetchItem = function(schemaName, id) { + debug('fetchItem'); + if (!schemaName) return Promise.reject(createError(400, 'expected schemaName')); + if (!id) return Promise.reject(createError(400, 'expected id')); + + return fs.readFileProm(`../data/${schemaName}/${id}.json`) + .then(file => { return JSON.parse(file); }) + .catch(err => { return Promise.reject(createError(404, err.message)); }); +}; + +exports.createItem = function(schemaName, item) { + debug('createItem'); + if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); + if (!item) return Promise.reject(createError(400, 'expected item')); + + return fs.writeFileProm(`../data/${schemaName}/${item.id}.json`) + .then(item => { return JSON.parse(item); }) + .catch(err => { return Promise.reject(createError(404, err.message)); }); +}; + +exports.getItemList = function(schemaName) { + debug('getItemList'); + if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); + return fs.readdirProm(`../data/${schemaName}`) + .then(list => { return list.map(filename => filename.split('.json')[0]); }) + .catch(err => { return Promise.reject(createError(404, err.message)); }); +}; diff --git a/lab-yana/model/blog.js b/lab-yana/model/blog.js index e69de29..637aabc 100644 --- a/lab-yana/model/blog.js +++ b/lab-yana/model/blog.js @@ -0,0 +1,43 @@ +'use strict'; + +const storage = require('../lib/storage.js'); +const randomID = require('note-uuid'); +const debug = require('debug')('blog:blog'); + +function Blog(name, content) { + this.id = randomID.v1(); + this.name = name; + this.content = content; +} + +Blog.removeBlog = function(id) { + debug('removeBlog'); + return storage.deleteItem('blog', id); +}; + +Blog.fetchBlog = function(id) { + debug('fetchBlog'); + return storage.fetchItem('blog', id); +}; + +Blog.createBlog = function(blog) { + debug('createBlog'); + return storage.createItem('blog', blog); +}; + +Blog.getBlogList = function() { + debug('getBlogList'); + return storage.getItemList('schemaName'); +}; + +Blog.updateBlog = function(id, content) { + debug('updateBlog'); + let blog = storage.fetchItem('blog', id); + blog.id = id; + for (var key in blog) { + blog[key] = content[key]; + } + return storage.createItem('blog', blog); +}; + +module.exports = Blog; diff --git a/lab-yana/route/blog-router.js b/lab-yana/route/blog-router.js index e69de29..5488284 100644 --- a/lab-yana/route/blog-router.js +++ b/lab-yana/route/blog-router.js @@ -0,0 +1,41 @@ +'use strict'; + +const debug = require('debug')('blog:blog-router'); +const Blog = require('../model/blog.js'); +const parseJSON = require('body-parser').json(); +const Router = require('express').Router; +const blogRouter = new Router(); + +blogRouter.get('/api/blog/:id', function(req, res, next) { + debug('GET: /api/blog/:id'); + Blog.fetchBlog(req.params.id) + .then(blog => res.json(blog)) + .catch(next); +}); + +blogRouter.get('/api/blog', function(req, res, next) { + debug('GET: /api/blog/:id'); + Blog.getBlogList() + .then(list => res.json(list)) + .catch(next); +}); + +blogRouter.post('/api/blog', parseJSON, function(req, res, next) { + debug('POST: /api/blog'); + Blog.createBlog(req.body) + .then(blog => res.json(blog)) + .catch(next); +}); + +blogRouter.put('/api/blog/:id', parseJSON, function(req, res, next) { + debug('PUT: /api/blog/:id'); + Blog.updateBlog(req.params.id, req.body) + .then(blog => res.json(blog)) + .catch(next); +}); + +blogRouter.delete('/api/blog/:id', function(req, res, next) { + debug('DELETE: /api/blog/:id'); + Blog.removeBlog(req.params.id) + .catch(next); +}); diff --git a/lab-yana/server.js b/lab-yana/server.js index e69de29..70f0e6b 100644 --- a/lab-yana/server.js +++ b/lab-yana/server.js @@ -0,0 +1,20 @@ +'use strict'; + +const PORT = 3003; +const debug = require('debug')('blog:server'); +const express = require('express'); +const morgan = require('morgan'); + +const blogRouter = require('./route/blog-router.js'); +const cors = require('./lib/cors-middleware.js'); +const errors = require('./lib/error-middleware.js'); + +const app = express(); + +debug(morgan('dev')); + +app.use(blogRouter); +app.use(cors); +app.use(errors); + +app.listen(PORT, () => { debug(`server up: ${PORT}`); } ); From 16f81f31bc142774e39a7bc76a56516fce051a31 Mon Sep 17 00:00:00 2001 From: radenska Date: Tue, 28 Feb 2017 17:46:25 -0800 Subject: [PATCH 3/3] 12 functional tests added, all files pass linter test --- lab-yana/README.md | 15 +-- lab-yana/lib/cors-middleware.js | 4 +- lab-yana/lib/storage.js | 38 +++--- lab-yana/model/blog.js | 47 ++++--- lab-yana/route/blog-router.js | 9 +- lab-yana/server.js | 8 +- lab-yana/test/blog-routes-test.js | 214 ++++++++++++++++++++++++++++++ 7 files changed, 282 insertions(+), 53 deletions(-) diff --git a/lab-yana/README.md b/lab-yana/README.md index ad37df8..8de09c1 100644 --- a/lab-yana/README.md +++ b/lab-yana/README.md @@ -1,12 +1,12 @@ -### My Fisher Price First Express API for Blogs!!!! +### My Fisher Price Second Express API for Blogs!!!! -This is a single resource API using express middleware to make creating a server and routing requests much more efficient and painless. It allows you to upload a blog entry, look at stored blog entries, see a list of available blog entries, and delete a blog entry. +This is a single resource API using express middleware to make creating a server and routing requests much more efficient and painless. It allows you to upload a blog entry, look at stored blog entries, and see a list of available blog entries. As a dev, you can also delete a blog entry. ### Directions for Use * make sure you have [node.js and npm installed](https://docs.npmjs.com/getting-started/installing-node), then in a terminal window, clone the code: ``` - $ git clone https://github.com/radenska/11-single_resource_express_api.git + $ git clone https://github.com/radenska/12-express_middleware.git ``` * run npm i: @@ -30,15 +30,12 @@ This is a single resource API using express middleware to make creating a server ``` $ http :3003/api/blog ``` - - delete a blog entry: - ``` - $ http DELETE :3003/api/blog/blogID - ``` - update a blog entry (if you don't include the id, a new entry will be created instead): ``` - $ http POST :3003/api/blog id="blogID" name="new name" content="new content" + $ http POST :3003/api/blog?id="blogID" name="new name" content="new content" ``` That's it! -##### Created by Yana Radenska +#### Created by Yana Radenska +##### [_Yana's GitHub Repository_](https://github.com/radenska) diff --git a/lab-yana/lib/cors-middleware.js b/lab-yana/lib/cors-middleware.js index 37c8062..6661797 100644 --- a/lab-yana/lib/cors-middleware.js +++ b/lab-yana/lib/cors-middleware.js @@ -2,6 +2,6 @@ module.exports = function(req, res, next) { res.append('Access-Control-Allow-Origin', '*'); - res.append('Access-Control-Allow-Headers', '#'); + res.append('Access-Control-Allow-Headers', '*'); next(); -} +}; diff --git a/lab-yana/lib/storage.js b/lab-yana/lib/storage.js index dcb7273..c4b893a 100644 --- a/lab-yana/lib/storage.js +++ b/lab-yana/lib/storage.js @@ -1,18 +1,20 @@ 'use strict'; -const debug = require('debug'); -const createError = require('http-error'); +const debug = require('debug')('blog:storage'); +const createError = require('http-errors'); const Promise = require('bluebird'); -const fs = require('fs').promisifyAll( {suffix: 'Prom'} ); +const fs = Promise.promisifyAll(require('fs'), {suffix: 'Prom'} ); +const url = `${__dirname}/../data/`; -module.exports = exports = {} +module.exports = exports = {}; -exports.deleteItem = function(schemaName, id) { - debug('deleteItem'); +exports.createItem = function(schemaName, item) { + debug('createItem'); if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); - if (!id) return Promise.reject(createError(400, 'expected id')); + if (!item) return Promise.reject(createError(400, 'expected item')); - return fs.unlinkProm(`../data/${schemaName}/${id}.json`) + return fs.writeFileProm(`${url}${schemaName}/${item.id}.json`, JSON.stringify(item)) + .then( () => item) .catch(err => { return Promise.reject(createError(404, err.message)); }); }; @@ -21,25 +23,27 @@ exports.fetchItem = function(schemaName, id) { if (!schemaName) return Promise.reject(createError(400, 'expected schemaName')); if (!id) return Promise.reject(createError(400, 'expected id')); - return fs.readFileProm(`../data/${schemaName}/${id}.json`) - .then(file => { return JSON.parse(file); }) + return fs.readFileProm(`${url}${schemaName}/${id}.json`) + .then(file => { + try { return JSON.parse(file.toString()); } + catch (err) {return Promise.reject(createError(400, err.message)); } + }) .catch(err => { return Promise.reject(createError(404, err.message)); }); }; -exports.createItem = function(schemaName, item) { - debug('createItem'); +exports.deleteItem = function(schemaName, id) { + debug('deleteItem'); if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); - if (!item) return Promise.reject(createError(400, 'expected item')); + if (!id) return Promise.reject(createError(400, 'expected id')); - return fs.writeFileProm(`../data/${schemaName}/${item.id}.json`) - .then(item => { return JSON.parse(item); }) - .catch(err => { return Promise.reject(createError(404, err.message)); }); + return fs.unlinkProm(`${url}${schemaName}/${id}.json`) + .catch(err => { return Promise.reject(createError(404, err.message)); }); }; exports.getItemList = function(schemaName) { debug('getItemList'); if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); - return fs.readdirProm(`../data/${schemaName}`) + return fs.readdirProm(`${url}${schemaName}`) .then(list => { return list.map(filename => filename.split('.json')[0]); }) .catch(err => { return Promise.reject(createError(404, err.message)); }); }; diff --git a/lab-yana/model/blog.js b/lab-yana/model/blog.js index 637aabc..17a4f55 100644 --- a/lab-yana/model/blog.js +++ b/lab-yana/model/blog.js @@ -1,18 +1,27 @@ 'use strict'; const storage = require('../lib/storage.js'); -const randomID = require('note-uuid'); +const randomID = require('node-uuid'); const debug = require('debug')('blog:blog'); +const createError = require('http-errors'); + +const Blog = module.exports = function (name, content) { + if (!name) throw createError(400, 'name expected'); + if (!content) throw createError(400, 'content expecte'); -function Blog(name, content) { this.id = randomID.v1(); this.name = name; this.content = content; -} +}; -Blog.removeBlog = function(id) { - debug('removeBlog'); - return storage.deleteItem('blog', id); +Blog.createBlog = function(_blog) { + debug('createBlog'); + try { + let blog = new Blog(_blog.name, _blog.content); + return storage.createItem('blog', blog); + } catch(err) { + return Promise.reject(createError(400, err.message)); + } }; Blog.fetchBlog = function(id) { @@ -20,24 +29,26 @@ Blog.fetchBlog = function(id) { return storage.fetchItem('blog', id); }; -Blog.createBlog = function(blog) { - debug('createBlog'); - return storage.createItem('blog', blog); +Blog.removeBlog = function(id) { + debug('removeBlog'); + return storage.deleteItem('blog', id); }; Blog.getBlogList = function() { debug('getBlogList'); - return storage.getItemList('schemaName'); + return storage.getItemList('blog'); }; Blog.updateBlog = function(id, content) { debug('updateBlog'); - let blog = storage.fetchItem('blog', id); - blog.id = id; - for (var key in blog) { - blog[key] = content[key]; - } - return storage.createItem('blog', blog); + return storage.fetchItem('blog', id) + .catch(err => Promise.reject(createError(404, err.message))) + .then(blog => { + if(!content.name && !content.content) return Promise.reject(createError(400, 'bad request')); //to make sure we're being given a valid update that has either a name or content property + for (var key in blog) { + if (key === 'id') continue; + if (content[key]) blog[key] = content[key]; + } + return storage.createItem('blog', blog); + }); }; - -module.exports = Blog; diff --git a/lab-yana/route/blog-router.js b/lab-yana/route/blog-router.js index 5488284..4a944c6 100644 --- a/lab-yana/route/blog-router.js +++ b/lab-yana/route/blog-router.js @@ -27,9 +27,9 @@ blogRouter.post('/api/blog', parseJSON, function(req, res, next) { .catch(next); }); -blogRouter.put('/api/blog/:id', parseJSON, function(req, res, next) { - debug('PUT: /api/blog/:id'); - Blog.updateBlog(req.params.id, req.body) +blogRouter.put('/api/blog', parseJSON, function(req, res, next) { + debug('PUT: /api/blog'); + Blog.updateBlog(req.query.id, req.body) .then(blog => res.json(blog)) .catch(next); }); @@ -37,5 +37,8 @@ blogRouter.put('/api/blog/:id', parseJSON, function(req, res, next) { blogRouter.delete('/api/blog/:id', function(req, res, next) { debug('DELETE: /api/blog/:id'); Blog.removeBlog(req.params.id) + .then( () => req.params.id) .catch(next); }); + +module.exports = blogRouter; diff --git a/lab-yana/server.js b/lab-yana/server.js index 70f0e6b..7f6f5e9 100644 --- a/lab-yana/server.js +++ b/lab-yana/server.js @@ -1,6 +1,5 @@ 'use strict'; -const PORT = 3003; const debug = require('debug')('blog:server'); const express = require('express'); const morgan = require('morgan'); @@ -9,12 +8,13 @@ const blogRouter = require('./route/blog-router.js'); const cors = require('./lib/cors-middleware.js'); const errors = require('./lib/error-middleware.js'); +const PORT = 3003; const app = express(); -debug(morgan('dev')); - -app.use(blogRouter); +//sequence is important for next() to work as intended! +app.use(morgan('dev')); app.use(cors); +app.use(blogRouter); app.use(errors); app.listen(PORT, () => { debug(`server up: ${PORT}`); } ); diff --git a/lab-yana/test/blog-routes-test.js b/lab-yana/test/blog-routes-test.js index e69de29..947557e 100644 --- a/lab-yana/test/blog-routes-test.js +++ b/lab-yana/test/blog-routes-test.js @@ -0,0 +1,214 @@ +'use strict'; + +const expect = require('chai').expect; +const request = require('superagent'); +const Blog = require('../model/blog.js'); +const url = 'http://localhost:3003'; +const testBlog = { name: 'test name', content: 'test content' }; +const exec = require('child_process').exec; + +var list = []; +var cmd = 'rm data/blog/*'; + +//run to clear data/blog directory for list tests +before(done => + { exec(cmd, err => + { if (err) done(err); + done(); + }); +}); + +require('../server.js'); + +describe('Blog Routes', function() { + describe('ALL METHODS /someEntirelyWrongRoute', function() { + it('should return a 404 not found error with GET', function(done) { + request.get(`${url}/reallyWrongRoute`) + .end(err => { + expect(err.status).to.equal(404); + done(); + }); + }); + it('should return a 404 not found error with PUT', function(done) { + request.put(`${url}/reallyWrongRoute`) + .end(err => { + expect(err.status).to.equal(404); + done(); + }); + }); + it('should return a 404 not found with POST', function(done) { + request.post(`${url}/reallyWrongRoute`) + .end(err => { + expect(err.status).to.equal(404); + done(); + }); + }); + }); + describe('GET: /api/blog/:id', function() { + before(done => { + Blog.createBlog(testBlog) + .then(blog => { + this.tempBlog = blog; + done(); + }).catch(err => done(err)); + }); + after(done => { + Blog.removeBlog(this.tempBlog.id).then( () => done()).catch(err => done(err)); + }); + it('should return a blog entry', done => { + request.get(`${url}/api/blog/${this.tempBlog.id}`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal(this.tempBlog.name); + expect(res.body.content).to.equal(this.tempBlog.content); + expect(res.body.id).to.equal(this.tempBlog.id); + done(); + }); + }); + }); + describe('GET: /api/blog/wrongID', function() { + it('should return a 404 not found error', function(done) { + request.get(`${url}/api/blog/terriblyWrongID`) + .end(err => { + expect(err.status).to.equal(404); + done(); + }); + }); + }); + describe('GET: /api/blog', function() { + before(done => { + request.post(`${url}/api/blog`) + .send(testBlog) + .end((err, res) => { + if (err) return done(err); + list.push(res.body.id); + }); + request.post(`${url}/api/blog`) + .send(testBlog) + .end((err, res) => { + if (err) return done(err); + list.push(res.body.id); + }); + request.post(`${url}/api/blog`) + .send(testBlog) + .end((err, res) => { + if (err) return done(err); + list.push(res.body.id); + }); + done(); + }); + after(done => { + exec(cmd, err => + { if (err) done(err); + done(); + }); + }); + it('should return a list of available blog IDs', done => { + request.get(`${url}/api/blog`) + .end((err, res) => { + if (err) return done(err); + console.log('res body', res.body); + console.log('list', list); + expect(res.body).to.be.an('array'); + expect(list.indexOf(res.body[0])).to.not.equal(-1); //async is killing me!!!!! ;-) + expect(list.indexOf(res.body[1])).to.not.equal(-1); //these three statements make sure that the IDs generated from the get are present in the comparison list, since they are not necessarily in the same order, so I can't just compare the two arrays + expect(list.indexOf(res.body[2])).to.not.equal(-1); + done(); + }); + }); + }); + describe('POST: /api/blog', function() { + it('should return a blog entry', function(done) { + request.post(`${url}/api/blog`) + .send(testBlog) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.content).to.equal(testBlog.content); + expect(res.body.name).to.equal(testBlog.name); + done(); + }); + }); + describe('without a valid name property', function() { + it('should return a 400 error', function(done) { + request.post(`${url}/api/blog/`) + .send( { content: 'nameless content' } ) + .end(err => { + expect(err.status).to.equal(400); + done(); + }); + }); + }); + describe('without a valid content property', function() { + it('should return a 400 error', function(done) { + request.post(`${url}/api/blog`) + .send( { name: 'contentless name'} ) + .end(err => { + expect(err.status).to.equal(400); + done(); + }); + }); + }); + }); + describe('PUT: /api/blog', function() { + var updatedBlog = { name: 'new name', content: 'new content' }; + describe('with a valid id and body', () => { + before(done => { + Blog.createBlog(testBlog) + .then(blog => { + this.tempBlog = blog; + done(); + }).catch(err => done(err)); + }); + after(done => { + Blog.removeBlog(this.tempBlog.id) + .then( () => done()).catch(err => done(err)); + }); + it('should return an updated blog entry', done => { + request.put(`${url}/api/blog?id=${this.tempBlog.id}`) + .send(updatedBlog) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal(updatedBlog.name); + expect(res.body.content).to.equal(updatedBlog.content); + expect(res.body.id).to.equal(this.tempBlog.id); + done(); + }); + }); + }); + describe('without a valid id', function() { + it('should return a 404 not found', function(done) { + request.put(`${url}/api/blog?id="oops"`) + .send(updatedBlog) + .end(err => { + expect(err.status).to.equal(404); + done(); + }); + }); + }); + describe('without a valid body', () => { + before(done => { + Blog.createBlog(testBlog) + .then(blog => { + this.tempBlog = blog; + done(); + }).catch(err => done(err)); + }); + after(done => { + Blog.removeBlog(this.tempBlog.id) + .then( () => done()).catch(err => done(err)); + }); + let badObject = 'bob'; + it('should return a 400 bad request', done => { + request.put(`${url}/api/blog?id=${this.tempBlog.id}`) + .send(badObject) + .end(err => { + expect(err.status).to.equal(400); + done(); + }); + }); + }); + }); +});