WIP - first pass at modernising the api layer
New file |
| | |
| | | # EditorConfig helps developers define and maintain consistent |
| | | # coding styles between different editors and IDEs |
| | | # editorconfig.org |
| | | |
| | | root = true |
| | | |
| | | |
| | | [*] |
| | | |
| | | # Change these settings to your own preference |
| | | indent_style = space |
| | | indent_size = 2 |
| | | |
| | | # We recommend you to keep these unchanged |
| | | end_of_line = lf |
| | | charset = utf-8 |
| | | trim_trailing_whitespace = true |
| | | insert_final_newline = true |
| | | |
| | | [*.md] |
| | | trim_trailing_whitespace = false |
New file |
| | |
| | | node_modules |
| | | public |
| | | .tmp |
| | | .idea |
| | | client/bower_components |
| | | dist |
| | | /server/config/local.env.js |
| | | npm-debug.log |
| | | reports |
| | | *.log |
New file |
| | |
| | | FROM node:4.4.7-slim |
| | | |
| | | RUN mkdir -p /usr/src/app |
| | | WORKDIR /usr/src/app |
| | | |
| | | COPY package.json /usr/src/app/ |
| | | RUN npm install --production |
| | | COPY . /usr/src/app |
| | | |
| | | EXPOSE 9000 |
| | | |
| | | CMD [ "npm", "start" ] |
New file |
| | |
| | | // Generated on 2016-08-09 using generator-angular-fullstack 2.1.1 |
| | | 'use strict'; |
| | | |
| | | module.exports = function (grunt) { |
| | | var localConfig; |
| | | try { |
| | | localConfig = require('./server/config/local.env'); |
| | | } catch(e) { |
| | | localConfig = {}; |
| | | } |
| | | |
| | | // Load grunt tasks automatically, when needed |
| | | require('jit-grunt')(grunt, { |
| | | express: 'grunt-express-server' |
| | | }); |
| | | |
| | | // Time how long tasks take. Can help when optimizing build times |
| | | require('time-grunt')(grunt); |
| | | |
| | | grunt.task.loadTasks('./tasks'); |
| | | |
| | | // Define the configuration for all the tasks |
| | | grunt.initConfig({ |
| | | |
| | | // Project settings |
| | | pkg: grunt.file.readJSON('package.json'), |
| | | express: { |
| | | options: { |
| | | port: process.env.PORT || 9000 |
| | | }, |
| | | dev: { |
| | | options: { |
| | | script: 'server/app.js', |
| | | debug: true |
| | | } |
| | | }, |
| | | prod: { |
| | | options: { |
| | | script: 'dist/server/app.js' |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // Make sure code styles are up to par and there are no obvious mistakes |
| | | jshint: { |
| | | options: { |
| | | reporter: require('jshint-stylish') |
| | | }, |
| | | server: { |
| | | options: { |
| | | jshintrc: 'server/.jshintrc' |
| | | }, |
| | | src: [ |
| | | 'server/**/*.js', |
| | | '!server/**/*.spec.js' |
| | | ] |
| | | }, |
| | | serverTest: { |
| | | options: { |
| | | jshintrc: 'server/.jshintrc' |
| | | }, |
| | | src: ['server/**/*.spec.js'] |
| | | }, |
| | | ci_server: { |
| | | options: { |
| | | jshintrc: 'server/.jshintrc', |
| | | reporter: require('jshint-jenkins-checkstyle-reporter'), |
| | | reporterOutput: 'reports/server/linting/jshint-server.xml' |
| | | }, |
| | | src: [ |
| | | 'server/**/*.js', |
| | | 'server/**/*.spec.js' |
| | | ] |
| | | }, |
| | | bm_client: { |
| | | options: { |
| | | jshintrc: 'client/.jshintrc', |
| | | reporter: require('jshint-junit-reporter'), |
| | | reporterOutput: 'reports/client/linting/jshint-junit-client.xml' |
| | | }, |
| | | src: [ |
| | | '<%= yeoman.client %>/app/**/*.js', |
| | | '<%= yeoman.client %>/app/**/*.spec.js', |
| | | '<%= yeoman.client %>/app/**/*.mock.js' |
| | | ] |
| | | }, |
| | | bm_server: { |
| | | options: { |
| | | jshintrc: 'server/.jshintrc', |
| | | reporter: require('jshint-junit-reporter'), |
| | | reporterOutput: 'reports/server/linting/jshint-junit-server.xml' |
| | | }, |
| | | src: [ |
| | | 'server/**/*.js', |
| | | 'server/**/*.spec.js' |
| | | ] |
| | | } |
| | | }, |
| | | |
| | | // Empties folders to start fresh |
| | | clean: { |
| | | server: '.tmp', |
| | | karmareports: 'reports/client/karma/**', |
| | | mochareports: 'reports/server/mocha/**', |
| | | lint: 'reports/{server,client}/jshint/**', |
| | | coverage: 'reports/{server,client}/coverage/**' |
| | | }, |
| | | |
| | | // Debugging with node inspector |
| | | 'node-inspector': { |
| | | custom: { |
| | | options: { |
| | | 'web-host': 'localhost' |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // Use nodemon to run server in debug mode with an initial breakpoint |
| | | nodemon: { |
| | | debug: { |
| | | script: 'server/app.js', |
| | | options: { |
| | | nodeArgs: ['--debug-brk'], |
| | | env: { |
| | | PORT: process.env.PORT || 9000 |
| | | }, |
| | | callback: function (nodemon) { |
| | | nodemon.on('log', function (event) { |
| | | console.log(event.colour); |
| | | }); |
| | | |
| | | // opens browser on initial server start |
| | | nodemon.on('config:update', function () { |
| | | setTimeout(function () { |
| | | require('open')('http://localhost:8080/debug?port=5858'); |
| | | }, 500); |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // Run some tasks in parallel to speed up the build process |
| | | concurrent: { |
| | | server: [ |
| | | ], |
| | | test: [ |
| | | ], |
| | | debug: { |
| | | tasks: [ |
| | | 'nodemon', |
| | | 'node-inspector' |
| | | ], |
| | | options: { |
| | | logConcurrentOutput: true |
| | | } |
| | | }, |
| | | dist: [ |
| | | ] |
| | | }, |
| | | |
| | | mochaTest: { |
| | | terminal: { |
| | | options: { |
| | | reporter: 'spec', |
| | | require: 'tasks/blanket' |
| | | }, |
| | | src: ['server/**/*.spec.js'] |
| | | }, |
| | | junit: { |
| | | options: { |
| | | reporter: 'mocha-junit-reporter', |
| | | // require: require("blanket"), |
| | | require: 'tasks/blanket' |
| | | }, |
| | | src: ['server/**/*.spec.js'] |
| | | }, |
| | | html: { |
| | | options: { |
| | | // reporters are ['html-cov', 'json-cov', 'travis-cov', 'mocha-lcov-reporter', 'mocha-cobertura-reporter'], |
| | | reporter: 'html-cov', |
| | | quiet: true, |
| | | captureFile: 'reports/server/coverage/cobertura-report.html' |
| | | }, |
| | | src: ['server/**/*.spec.js'] |
| | | }, |
| | | cobertura: { |
| | | options: { |
| | | reporter: 'mocha-cobertura-reporter', |
| | | // use the quiet flag to suppress the mocha console output |
| | | quiet: true, |
| | | // specify a destination file to capture the mocha |
| | | // output (the quiet option does not suppress this) |
| | | captureFile: 'reports/server/coverage/cobertura-report.xml' |
| | | }, |
| | | src: ['server/**/*.spec.js'] |
| | | }, |
| | | travis: { |
| | | options: { |
| | | reporter: 'travis-cov', |
| | | quiet: false |
| | | }, |
| | | src: ['server/**/*.spec.js'] |
| | | } |
| | | }, |
| | | |
| | | env: { |
| | | test: { |
| | | NODE_ENV: 'test' |
| | | }, |
| | | dev: { |
| | | NODE_ENV: 'development' |
| | | }, |
| | | ci: { |
| | | NODE_ENV: 'ci' |
| | | }, |
| | | si: { |
| | | NODE_ENV: 'si' |
| | | }, |
| | | prod: { |
| | | NODE_ENV: 'production' |
| | | }, |
| | | jenkins: { |
| | | MOCHA_FILE: 'reports/server/mocha/test-results.xml' |
| | | }, |
| | | all: localConfig |
| | | }, |
| | | }); |
| | | |
| | | // Used for delaying livereload until after server has restarted |
| | | grunt.registerTask('wait', function () { |
| | | grunt.log.ok('Waiting for server reload...'); |
| | | |
| | | var done = this.async(); |
| | | |
| | | setTimeout(function () { |
| | | grunt.log.writeln('Done waiting!'); |
| | | done(); |
| | | }, 1500); |
| | | }); |
| | | |
| | | grunt.registerTask('express-keepalive', 'Keep grunt running', function() { |
| | | this.async(); |
| | | }); |
| | | |
| | | grunt.registerTask('build-image', 'Build the image', function(imageId) { |
| | | var shell = require("shelljs"); |
| | | grunt.log.ok('BUILDING IMAGE'); |
| | | if (!imageId) { |
| | | grunt.fail.warn('must supply an imageId to build'); |
| | | } |
| | | var rc = shell.exec('docker build -t todolist:' + imageId + ' -f ./dist/Dockerfile ./dist').code; |
| | | if (rc > 0){ |
| | | grunt.fail.warn("DOCKER FAILURE") |
| | | } |
| | | }); |
| | | |
| | | grunt.registerTask('deploy', 'deploy the node js app to a docker container and start it in the correct mode', function(target_env, build_tag) { |
| | | grunt.log.ok('this task must run on a host that has the Docker Daemon running on it'); |
| | | var ports = { |
| | | ci: '9001', |
| | | si: '9002', |
| | | production: '80' |
| | | }; |
| | | |
| | | if (target_env === undefined || build_tag === undefined){ |
| | | grunt.fail.warn('Required param not set - use grunt deploy\:\<target\>\:\<tag\>'); |
| | | } else { |
| | | var shell = require("shelljs"); |
| | | grunt.log.ok('STOPPING AND REMOVING EXISTING CONTAINERS'); |
| | | shell.exec('docker stop todolist-'+ target_env + ' && docker rm todolist-'+ target_env); |
| | | |
| | | grunt.log.ok('DEPLOYING ' + target_env + ' CONTAINER'); |
| | | if (target_env === 'ci'){ |
| | | var rc = shell.exec('docker run -t -d --name todolist-' + target_env + ' -p ' + ports[target_env]+ ':'+ports[target_env]+' --env NODE_ENV=' + target_env + ' todolist:' + build_tag); |
| | | if (rc > 0){ |
| | | grunt.fail.warn("DOCKER FAILURE") |
| | | } |
| | | } else { |
| | | // ensure mongo is up |
| | | var isMongo = shell.exec('docker ps | grep devops-mongo').code; |
| | | if (isMongo > 0){ |
| | | grunt.log.ok('DEPLOYING Mongodb CONTAINER FIRST'); |
| | | shell.exec('docker run --name devops-mongo -p 27017:27017 -d mongo'); |
| | | } |
| | | var rc = shell.exec('docker run -t -d --name todolist-' + target_env + ' --link devops-mongo:mongo.server -p ' |
| | | + ports[target_env]+ ':' + ports[target_env] + ' --env NODE_ENV=' + target_env + ' todolist:' + build_tag).code; |
| | | if (rc > 0){ |
| | | grunt.fail.warn("DOCKER FAILURE"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | }); |
| | | |
| | | grunt.registerTask('serve', function (target) { |
| | | if (target === 'dist') { |
| | | return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'open', 'express-keepalive']); |
| | | } |
| | | |
| | | if (target === 'debug') { |
| | | return grunt.task.run([ |
| | | 'clean:server', |
| | | 'env:all', |
| | | 'concurrent:server', |
| | | 'concurrent:debug' |
| | | ]); |
| | | } |
| | | |
| | | grunt.task.run([ |
| | | 'clean:server', |
| | | 'env:all', |
| | | 'env:' + (target || 'dev'), |
| | | 'express:dev', |
| | | 'wait', |
| | | 'open', |
| | | 'watch' |
| | | ]); |
| | | }); |
| | | |
| | | grunt.registerTask('test', function(target, environ) { |
| | | environ = environ !== undefined ? environ : 'test'; |
| | | var usePhantom = false; |
| | | if (environ === 'phantom') { |
| | | environ = 'test'; |
| | | usePhantom = true; |
| | | } |
| | | var reporter = 'terminal'; |
| | | var coverage = 'travis'; |
| | | if (target === 'server-jenkins') { |
| | | target = 'server'; |
| | | reporter = 'junit'; |
| | | coverage = 'cobertura'; |
| | | grunt.task.run(['env:jenkins']); |
| | | } |
| | | if (target === 'server') { |
| | | return grunt.task.run([ |
| | | 'clean:mochareports', |
| | | 'clean:coverage', |
| | | 'env:all', |
| | | 'env:'+environ, |
| | | 'mochaTest:' + reporter, |
| | | 'mochaTest:' + 'html', |
| | | 'mochaTest:' + coverage |
| | | ]); |
| | | } |
| | | |
| | | else grunt.task.run([ |
| | | 'test:server' |
| | | ]); |
| | | }); |
| | | |
| | | grunt.registerTask('build', [ |
| | | 'clean:dist', |
| | | 'concurrent:dist', |
| | | ]); |
| | | |
| | | grunt.registerTask('default', [ |
| | | 'newer:jshint', |
| | | 'test', |
| | | 'build' |
| | | ]); |
| | | }; |
New file |
| | |
| | | William Lacy |
| | | Donal Spring |
New file |
| | |
| | | { |
| | | "name": "todolist", |
| | | "version": "1.0.0", |
| | | "main": "server/app.js", |
| | | "dependencies": { |
| | | "body-parser": "1.5.2", |
| | | "composable-middleware": "0.3.0", |
| | | "compression": "1.0.11", |
| | | "connect-mongo": "0.8.2", |
| | | "cookie-parser": "1.0.1", |
| | | "ejs": "0.8.8", |
| | | "errorhandler": "1.0.2", |
| | | "express": "4.9.8", |
| | | "express-jwt": "3.4.0", |
| | | "express-session": "1.0.4", |
| | | "jsonwebtoken": "5.7.0", |
| | | "lodash": "2.4.2", |
| | | "method-override": "1.0.2", |
| | | "mongoose": "4.0.8", |
| | | "morgan": "1.0.1", |
| | | "serve-favicon": "2.0.1" |
| | | }, |
| | | "devDependencies": { |
| | | "blanket": "1.1.9", |
| | | "connect-livereload": "0.4.1", |
| | | "fs-extra": "0.30.0", |
| | | "grunt": "0.4.5", |
| | | "grunt-build-control": "0.4.0", |
| | | "grunt-concurrent": "0.5.0", |
| | | "grunt-contrib-clean": "0.5.0", |
| | | "grunt-contrib-concat": "0.4.0", |
| | | "grunt-contrib-copy": "0.5.0", |
| | | "grunt-contrib-jshint": "0.10.0", |
| | | "grunt-env": "0.4.4", |
| | | "grunt-express-server": "0.4.19", |
| | | "grunt-mocha-test": "0.10.2", |
| | | "grunt-newer": "0.7.0", |
| | | "grunt-ng-annotate": "0.2.3", |
| | | "grunt-node-inspector": "0.4.2", |
| | | "grunt-nodemon": "0.2.1", |
| | | "grunt-open": "0.2.3", |
| | | "grunt-protractor-runner": "1.2.1", |
| | | "grunt-rev": "0.1.0", |
| | | "grunt-svgmin": "0.4.0", |
| | | "grunt-usemin": "2.1.1", |
| | | "grunt-wiredep": "1.8.0", |
| | | "jasmine-reporters": "2.2.0", |
| | | "jit-grunt": "0.5.0", |
| | | "jshint-jenkins-checkstyle-reporter": "0.1.2", |
| | | "jshint-junit-reporter": "0.2.2", |
| | | "jshint-stylish": "0.1.5", |
| | | "mocha": "3.0.2", |
| | | "mocha-cobertura-reporter": "1.0.4", |
| | | "mocha-junit-reporter": "1.12.0", |
| | | "mocha-lcov-reporter": "1.2.0", |
| | | "open": "0.0.5", |
| | | "q": "1.4.1", |
| | | "request": "2.74.0", |
| | | "requirejs": "2.1.22", |
| | | "shelljs": "0.7.3", |
| | | "should": "3.3.2", |
| | | "supertest": "0.11.0", |
| | | "time-grunt": "0.3.2", |
| | | "travis-cov": "0.2.5" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8.0.0" |
| | | }, |
| | | "scripts": { |
| | | "start": "node server/app.js", |
| | | "test": "grunt test" |
| | | }, |
| | | "private": true |
| | | } |
New file |
| | |
| | | { |
| | | "node": true, |
| | | "esnext": true, |
| | | "bitwise": true, |
| | | "eqeqeq": true, |
| | | "immed": true, |
| | | "latedef": "nofunc", |
| | | "newcap": true, |
| | | "noarg": true, |
| | | "undef": true, |
| | | "smarttabs": true, |
| | | "asi": true, |
| | | "noempty": true, |
| | | "debug": false, |
| | | "globals": { |
| | | "describe": true, |
| | | "it": true, |
| | | "before": true, |
| | | "beforeEach": true, |
| | | "after": true, |
| | | "afterEach": true |
| | | } |
| | | } |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | var express = require('express'); |
| | | |
| | | var router = express.Router(); |
| | | |
| | | var controller = require('./todo.controller'); |
| | | |
| | | router.get('/', controller.index); |
| | | router.get('/:id', controller.show); |
| | | router.post('/', controller.create); |
| | | router.put('/:id', controller.update); |
| | | router.patch('/:id', controller.update); |
| | | router.delete('/:id', controller.destroy); |
| | | |
| | | module.exports = router; |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | var _ = require('lodash'); |
| | | var Todo = require('./todo.model'); |
| | | |
| | | // Get list of todos |
| | | exports.index = function(req, res) { |
| | | var biscuits; |
| | | Todo.find(function (err, todos) { |
| | | if(err) { return handleError(res, err); } |
| | | return res.status(200).json(todos); |
| | | }); |
| | | }; |
| | | |
| | | // Get a single todo |
| | | exports.show = function(req, res) { |
| | | if (!handleObjectId(req, res)) return; |
| | | Todo.findById(req.params.id, function (err, todo) { |
| | | if(err) { return handleError(res, err); } |
| | | if(!todo) { return res.status(404).send('Not Found'); } |
| | | return res.json(todo); |
| | | }); |
| | | }; |
| | | |
| | | // Creates a new todo in the DB. |
| | | exports.create = function(req, res) { |
| | | Todo.create(req.body, function(err, todo) { |
| | | if(err) { return handleError(res, err); } |
| | | return res.status(201).json(todo); |
| | | }); |
| | | }; |
| | | |
| | | // Updates an existing todo in the DB. |
| | | exports.update = function(req, res) { |
| | | if(req.body._id) { delete req.body._id; } |
| | | if (!handleObjectId(req, res)) return; |
| | | Todo.findById(req.params.id.toString(), function (err, todo) { |
| | | if (err) { return handleError(res, err); } |
| | | if(!todo) { return res.status(404).send('Not Found'); } |
| | | var updated = _.merge(todo, req.body); |
| | | updated.save(function (err) { |
| | | if (err) { return handleError(res, err); } |
| | | return res.status(200).json(todo); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // Deletes a todo from the DB. |
| | | exports.destroy = function(req, res) { |
| | | if (!handleObjectId(req, res)) return; |
| | | Todo.findById(req.params.id, function (err, todo) { |
| | | if(err) { return handleError(res, err); } |
| | | if(!todo) { return res.status(404).send('Not Found'); } |
| | | todo.remove(function(err) { |
| | | if(err) { return handleError(res, err); } |
| | | return res.status(204).send('No Content'); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | function handleError(res, err) { |
| | | return res.status(500).send(err); |
| | | } |
| | | |
| | | function handleObjectId(req, res) { |
| | | // check if it is a valid ObjectID to prevent cast error |
| | | if (!req.params || !req.params.id || !req.params.id.match(/^[0-9a-fA-F]{24}$/)) { |
| | | res.status(400).send('not a valid mongo object id'); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | var mongoose = require('mongoose'), |
| | | Schema = mongoose.Schema; |
| | | |
| | | var TodoSchema = new Schema({ |
| | | title: String, |
| | | completed: Boolean |
| | | }); |
| | | |
| | | module.exports = mongoose.model('Todo', TodoSchema); |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | var app = require('../../app'); |
| | | var request = require('supertest'); |
| | | require('should'); |
| | | |
| | | describe('GET /api/todos', function() { |
| | | |
| | | it('should respond with JSON array', function(done) { |
| | | request(app) |
| | | .get('/api/todos') |
| | | .expect(200) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.body.should.be.instanceof(Array); |
| | | done(); |
| | | }); |
| | | }); |
| | | |
| | | }); |
| | | |
| | | describe('POST /api/todos', function() { |
| | | it('should create the todo and return with the todo', function(done) { |
| | | request(app) |
| | | .post('/api/todos') |
| | | .send({title: 'learn about endpoint/server side testing', completed: false}) |
| | | .expect(201) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.body.should.have.property('_id'); |
| | | res.body.title.should.equal('learn about endpoint/server side testing'); |
| | | res.body.completed.should.equal(false); |
| | | done(); |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | describe('GET /api/todos/:id', function() { |
| | | var todoId; |
| | | beforeEach(function createObjectToUpdate(done) { |
| | | request(app) |
| | | .post('/api/todos') |
| | | .send({title: 'learn about endpoint/server side testing', completed: false}) |
| | | .expect(201) |
| | | .expect('Content-Type', /json/) |
| | | .end(function (err, res) { |
| | | if (err) return done(err); |
| | | todoId = res.body._id; |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should update the todo', function (done) { |
| | | request(app) |
| | | .get('/api/todos/' + todoId) |
| | | .expect(200) |
| | | .expect('Content-Type', /json/) |
| | | .end(function (err, res) { |
| | | if (err) return done(err); |
| | | res.body._id.should.equal(todoId); |
| | | res.body.title.should.equal('learn about endpoint/server side testing'); |
| | | res.body.completed.should.equal(false); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 404 for valid mongo object id that does not exist', function(done){ |
| | | request(app) |
| | | .get('/api/todos/' + 'abcdef0123456789ABCDEF01') |
| | | .expect(404) |
| | | .end(function(err) { |
| | | if (err) return done(err); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 400 for invalid object ids', function(done){ |
| | | request(app) |
| | | .get('/api/todos/' + 123) |
| | | .expect(400) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.text.should.equal('not a valid mongo object id') |
| | | done(); |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | describe('PUT /api/todos/:id', function() { |
| | | var todoId; |
| | | beforeEach(function createObjectToUpdate(done){ |
| | | request(app) |
| | | .post('/api/todos') |
| | | .send({title: 'learn about endpoint/server side testing', completed: false}) |
| | | .expect(201) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | todoId = res.body._id; |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should update the todo', function(done) { |
| | | request(app) |
| | | .put('/api/todos/' + todoId) |
| | | .send({title: 'LOVE endpoint/server side testing!', completed: true}) |
| | | .expect(200) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.body.should.have.property('_id'); |
| | | res.body.title.should.equal('LOVE endpoint/server side testing!'); |
| | | res.body.completed.should.equal(true); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 404 for valid mongo object id that does not exist', function(done){ |
| | | request(app) |
| | | .put('/api/todos/' + 'abcdef0123456789ABCDEF01') |
| | | .send({title: 'LOVE endpoint/server side testing!', completed: true}) |
| | | .expect(404) |
| | | .end(function(err) { |
| | | if (err) return done(err); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 400 for invalid object ids', function(done){ |
| | | request(app) |
| | | .put('/api/todos/' + 123) |
| | | .send({title: 'LOVE endpoint/server side testing!', completed: true}) |
| | | .expect(400) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.text.should.equal('not a valid mongo object id') |
| | | done(); |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | describe('DELETE /api/todos/:id', function() { |
| | | var todoId; |
| | | beforeEach(function createObjectToUpdate(done){ |
| | | request(app) |
| | | .post('/api/todos') |
| | | .send({title: 'learn about endpoint/server side testing', completed: false}) |
| | | .expect(201) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | todoId = res.body._id; |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should delete the todo', function(done) { |
| | | request(app) |
| | | .delete('/api/todos/' + todoId) |
| | | .expect(204) |
| | | .end(function(err) { |
| | | if (err) return done(err); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 404 for valid mongo object id that does not exist', function(done){ |
| | | request(app) |
| | | .delete('/api/todos/' + 'abcdef0123456789ABCDEF01') |
| | | .expect(404) |
| | | .end(function(err) { |
| | | if (err) return done(err); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 400 for invalid object ids', function(done){ |
| | | request(app) |
| | | .delete('/api/todos/' + 123) |
| | | .send({title: 'LOVE endpoint/server side testing!', completed: true}) |
| | | .expect(400) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.text.should.equal('not a valid mongo object id') |
| | | done(); |
| | | }); |
| | | }); |
| | | }); |
New file |
| | |
| | | /** |
| | | * Main application file |
| | | */ |
| | | |
| | | 'use strict'; |
| | | |
| | | // Set default node environment to development |
| | | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; |
| | | |
| | | var express = require('express'); |
| | | var config = require('./config/environment'); |
| | | |
| | | |
| | | |
| | | // Populate DB with sample data |
| | | if(config.seedDB) { require('./config/seed'); } |
| | | |
| | | // Setup server |
| | | var app = express(); |
| | | var server = require('http').createServer(app); |
| | | require('./config/express')(app); |
| | | |
| | | if (config.mocks && config.mocks.api) { |
| | | //add stubs |
| | | require('./mocks/mock-routes')(app); |
| | | } else { |
| | | var mongoose = require('mongoose'); |
| | | // Connect to database |
| | | mongoose.connect(config.mongo.uri, config.mongo.options); |
| | | mongoose.connection.on('error', function(err) { |
| | | console.error('MongoDB connection error: ' + err); |
| | | process.exit(-1); |
| | | } |
| | | ); |
| | | require('./routes')(app); |
| | | } |
| | | |
| | | // Start server |
| | | server.listen(config.port, config.ip, function () { |
| | | console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); |
| | | }); |
| | | |
| | | // Expose app |
| | | exports = module.exports = app; |
New file |
| | |
| | | /** |
| | | * Error responses |
| | | */ |
| | | |
| | | 'use strict'; |
| | | |
| | | module.exports[404] = function pageNotFound(req, res) { |
| | | var viewFilePath = '404'; |
| | | var statusCode = 404; |
| | | var result = { |
| | | status: statusCode |
| | | }; |
| | | |
| | | res.status(result.status); |
| | | res.render(viewFilePath, function (err) { |
| | | if (err) { return res.json(result, result.status); } |
| | | |
| | | res.render(viewFilePath); |
| | | }); |
| | | }; |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | // Test specific configuration |
| | | // =========================== |
| | | module.exports = { |
| | | // MongoDB connection options |
| | | mongo: { |
| | | uri: 'mongodb://mongo.server/todolist-ci' |
| | | }, |
| | | mocks: { |
| | | api: true |
| | | }, |
| | | seedDB: true, |
| | | port: process.env.PORT || 9001 |
| | | }; |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | // Development specific configuration |
| | | // ================================== |
| | | module.exports = { |
| | | // MongoDB connection options |
| | | mongo: { |
| | | uri: 'mongodb://mongo.server/todolist-dev' |
| | | }, |
| | | mocks: { |
| | | api: true |
| | | }, |
| | | seedDB: true |
| | | }; |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | var path = require('path'); |
| | | var _ = require('lodash'); |
| | | |
| | | var config; |
| | | // All configurations will extend these options |
| | | // ============================================ |
| | | var all = { |
| | | env: process.env.NODE_ENV, |
| | | |
| | | // Root path of server |
| | | root: path.normalize(__dirname + '/../../..'), |
| | | |
| | | // Server port |
| | | port: process.env.PORT || 9000, |
| | | |
| | | // Server IP |
| | | ip: process.env.IP || '0.0.0.0', |
| | | |
| | | // Should we populate the DB with sample data? |
| | | seedDB: false, |
| | | |
| | | // Secret for session, you will want to change this and make it an environment variable |
| | | secrets: { |
| | | session: 'todolist-secret' |
| | | }, |
| | | |
| | | // MongoDB connection options |
| | | mongo: { |
| | | options: { |
| | | db: { |
| | | safe: true |
| | | } |
| | | } |
| | | }, |
| | | |
| | | }; |
| | | |
| | | // Export the config object based on the NODE_ENV |
| | | // ============================================== |
| | | module.exports = _.merge( |
| | | all, |
| | | require('./' + process.env.NODE_ENV + '.js') || {}); |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | // Production specific configuration |
| | | // ================================= |
| | | module.exports = { |
| | | // Server IP |
| | | ip: process.env.OPENSHIFT_NODEJS_IP || |
| | | process.env.IP || |
| | | '0.0.0.0', |
| | | |
| | | // Server port |
| | | port: process.env.OPENSHIFT_NODEJS_PORT || |
| | | process.env.PORT || |
| | | 80, |
| | | |
| | | // MongoDB connection options |
| | | mongo: { |
| | | uri: process.env.MONGOLAB_URI || |
| | | process.env.MONGOHQ_URL || |
| | | process.env.OPENSHIFT_MONGODB_DB_URL+process.env.OPENSHIFT_APP_NAME || |
| | | 'mongodb://mongo.server/todolist-prod' |
| | | }, |
| | | seedDB: true |
| | | }; |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | // Test specific configuration |
| | | // =========================== |
| | | module.exports = { |
| | | // MongoDB connection options |
| | | mongo: { |
| | | uri: 'mongodb://mongo.server/todolist-si' |
| | | }, |
| | | seedDB: true, |
| | | port: process.env.PORT || 9002 |
| | | }; |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | // Test specific configuration |
| | | // =========================== |
| | | module.exports = { |
| | | // MongoDB connection options |
| | | mongo: { |
| | | uri: 'mongodb://mongo.server/todolist-test' |
| | | }, |
| | | seedDB: true, |
| | | port: process.env.PORT || 9000 |
| | | }; |
New file |
| | |
| | | /** |
| | | * Express configuration |
| | | */ |
| | | |
| | | 'use strict'; |
| | | |
| | | var express = require('express'); |
| | | var morgan = require('morgan'); |
| | | var compression = require('compression'); |
| | | var bodyParser = require('body-parser'); |
| | | var methodOverride = require('method-override'); |
| | | var cookieParser = require('cookie-parser'); |
| | | var errorHandler = require('errorhandler'); |
| | | var path = require('path'); |
| | | var config = require('./environment'); |
| | | var passport = require('passport'); |
| | | |
| | | module.exports = function(app) { |
| | | var env = app.get('env'); |
| | | |
| | | app.set('views', config.root + '/server/views'); |
| | | app.engine('html', require('ejs').renderFile); |
| | | app.set('view engine', 'html'); |
| | | app.use(compression()); |
| | | app.use(bodyParser.urlencoded({ extended: false })); |
| | | app.use(bodyParser.json()); |
| | | app.use(methodOverride()); |
| | | app.use(cookieParser()); |
| | | app.use(passport.initialize()); |
| | | if ('production' === env || 'si' === env || 'ci' === env) { |
| | | app.use(express.static(path.join(config.root, 'public'))); |
| | | app.set('appPath', path.join(config.root, 'public')); |
| | | app.use(morgan('dev')); |
| | | } |
| | | |
| | | if ('development' === env || 'test' === env) { |
| | | app.use(require('connect-livereload')()); |
| | | app.use(express.static(path.join(config.root, '.tmp'))); |
| | | app.use(express.static(path.join(config.root, 'client'))); |
| | | app.set('appPath', path.join(config.root, 'client')); |
| | | app.use(morgan('dev')); |
| | | app.use(errorHandler()); // Error handler - has to be last |
| | | } |
| | | }; |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | // Use local.env.js for environment variables that grunt will set when the server starts locally. |
| | | // Use for your api keys, secrets, etc. This file should not be tracked by git. |
| | | // |
| | | // You will need to set these on the server you deploy to. |
| | | |
| | | module.exports = { |
| | | DOMAIN: 'http://localhost:9000', |
| | | SESSION_SECRET: 'todolist-secret', |
| | | |
| | | // Control debug level for modules using visionmedia/debug |
| | | DEBUG: '' |
| | | }; |
New file |
| | |
| | | /** |
| | | * Populate DB with sample data on server start |
| | | * to disable, edit config/environment/index.js, and set `seedDB: false` |
| | | */ |
| | | |
| | | 'use strict'; |
| | | |
| | | var Todo = require('../api/todo/todo.model'); |
| | | |
| | | Todo.find({}).remove(function() { |
| | | Todo.create({ |
| | | title : 'Learn some stuff about Jenkins', |
| | | completed: true |
| | | }, { |
| | | title : 'Go for Coffee', |
| | | completed: false |
| | | }); |
| | | }); |
New file |
| | |
| | | [ |
| | | { |
| | | "request": { |
| | | "url": "^/todos", |
| | | "method": "GET" |
| | | }, |
| | | "response": { |
| | | "status": 200, |
| | | "headers": { |
| | | "Content-Type": "application/json", |
| | | "some-arb-HEADER": "for testing" |
| | | }, |
| | | "latency": 800, |
| | | "body": [ |
| | | { |
| | | "title": "Learn some stuff about Jenkins", |
| | | "_id": "abcdef1234567890abcdef12", |
| | | "completed": true |
| | | }, |
| | | { |
| | | "title": "Completed lab 1", |
| | | "_id": "abcdef1234567890abcdef13", |
| | | "completed": false |
| | | } |
| | | ] |
| | | } |
| | | }, |
| | | { |
| | | "request": { |
| | | "url": "^/todos/*", |
| | | "method": "PUT", |
| | | "headers": { |
| | | "Content-Type": "application/json" |
| | | } |
| | | }, |
| | | "response": { |
| | | "status": 200, |
| | | "headers": { |
| | | "Content-Type": "application/json" |
| | | }, |
| | | "latency": 300, |
| | | "body": { |
| | | "title": "whatever", |
| | | "_id": "abcdef1234567890abcdef12", |
| | | "completed": true |
| | | } |
| | | } |
| | | }, |
| | | { |
| | | "request": { |
| | | "url": "^/todos/*", |
| | | "method": "DELETE", |
| | | "headers": { |
| | | "Content-Type": "application/json" |
| | | } |
| | | }, |
| | | "response": { |
| | | "status": 204, |
| | | "headers": { |
| | | "Content-Type": "application/json" |
| | | }, |
| | | "latency": 150, |
| | | "body": {} |
| | | } |
| | | }, |
| | | { |
| | | "request": { |
| | | "url": "^/todos", |
| | | "method": "POST", |
| | | "headers": { |
| | | "Content-Type": "application/json" |
| | | } |
| | | }, |
| | | "response": { |
| | | "status": 201, |
| | | "headers": { |
| | | "Content-Type": "application/json" |
| | | }, |
| | | "latency": 445, |
| | | "body": { |
| | | "title": "some new thing", |
| | | "_id": "abcdef1234567890abcdef12", |
| | | "completed": false |
| | | } |
| | | } |
| | | } |
| | | ] |
New file |
| | |
| | | /** |
| | | * Stubbed Application routes |
| | | */ |
| | | |
| | | 'use strict'; |
| | | |
| | | var express = require('express'); |
| | | |
| | | var routerStub = express.Router(); |
| | | |
| | | function mockMongoId() { |
| | | function s4() { |
| | | return Math.floor((1 + Math.random()) * 0x10000) |
| | | .toString(16) |
| | | .substring(1); |
| | | } |
| | | return s4() + s4() + s4() + s4() + s4() + s4(); |
| | | } |
| | | |
| | | routerStub.get('/todos', function (req, res) { |
| | | // add timeout to test loading styles |
| | | setTimeout(function () { |
| | | return res.status(200).send([ |
| | | {"title": "Learn some stuff about Jenkins", "_id": mockMongoId(), "completed": true}, |
| | | {"title": "Go for Coffee", "_id": mockMongoId(), "completed": false} |
| | | ]); |
| | | }, 650); |
| | | }); |
| | | |
| | | routerStub.get('/todos/:id', function (req, res) { |
| | | setTimeout(function () { |
| | | var id = req.params.id; |
| | | return res.status(200).send({ |
| | | "title": "Learn some stuff about Jenkins", "_id": id, "completed": true |
| | | }); |
| | | }, 150); |
| | | }); |
| | | |
| | | routerStub.post('/todos', function (req, res) { |
| | | setTimeout(function () { |
| | | req.body._id = mockMongoId(); |
| | | return res.status(201).send(req.body); |
| | | }, 170); |
| | | }); |
| | | |
| | | routerStub.put('/todos/:id', function (req, res) { |
| | | setTimeout(function () { |
| | | req.body._id = req.params.id; |
| | | return res.status(200).send(req.body); |
| | | }, 130); |
| | | }); |
| | | |
| | | routerStub.delete('/todos/:id', function (req, res) { |
| | | setTimeout(function () { |
| | | return res.status(204).send(); |
| | | }, 100); |
| | | }); |
| | | |
| | | module.exports = function(app) { |
| | | app.use('/api', routerStub) |
| | | }; |
New file |
| | |
| | | 'use strict'; |
| | | |
| | | var app = require('../app'); |
| | | var request = require('supertest'); |
| | | require('should'); |
| | | |
| | | describe('GET /api/todos', function() { |
| | | |
| | | it('should respond with JSON array', function(done) { |
| | | request(app) |
| | | .get('/api/todos') |
| | | .expect(200) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.body.should.be.instanceof(Array); |
| | | done(); |
| | | }); |
| | | }); |
| | | |
| | | }); |
| | | |
| | | describe('POST /api/todos', function() { |
| | | it('should create the todo and return with the todo', function(done) { |
| | | request(app) |
| | | .post('/api/todos') |
| | | .send({title: 'learn about endpoint/server side testing', completed: false}) |
| | | .expect(201) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.body.should.have.property('_id'); |
| | | res.body.title.should.equal('learn about endpoint/server side testing'); |
| | | res.body.completed.should.equal(false); |
| | | done(); |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | describe('GET /api/todos/:id', function() { |
| | | var todoId; |
| | | beforeEach(function createObjectToUpdate(done) { |
| | | request(app) |
| | | .post('/api/todos') |
| | | .send({title: 'learn about endpoint/server side testing', completed: false}) |
| | | .expect(201) |
| | | .expect('Content-Type', /json/) |
| | | .end(function (err, res) { |
| | | if (err) return done(err); |
| | | todoId = res.body._id; |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should update the todo', function (done) { |
| | | request(app) |
| | | .get('/api/todos/' + todoId) |
| | | .expect(200) |
| | | .expect('Content-Type', /json/) |
| | | .end(function (err, res) { |
| | | if (err) return done(err); |
| | | res.body._id.should.equal(todoId); |
| | | res.body.title.should.equal('learn about endpoint/server side testing'); |
| | | res.body.completed.should.equal(false); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 404 for valid mongo object id that does not exist', function(done){ |
| | | request(app) |
| | | .get('/api/todos/' + 'abcdef0123456789ABCDEF01') |
| | | .expect(404) |
| | | .end(function(err) { |
| | | if (err) return done(err); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 400 for invalid object ids', function(done){ |
| | | request(app) |
| | | .get('/api/todos/' + 123) |
| | | .expect(400) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.text.should.equal('not a valid mongo object id') |
| | | done(); |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | describe('PUT /api/todos/:id', function() { |
| | | var todoId; |
| | | beforeEach(function createObjectToUpdate(done){ |
| | | request(app) |
| | | .post('/api/todos') |
| | | .send({title: 'learn about endpoint/server side testing', completed: false}) |
| | | .expect(201) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | todoId = res.body._id; |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should update the todo', function(done) { |
| | | request(app) |
| | | .put('/api/todos/' + todoId) |
| | | .send({title: 'LOVE endpoint/server side testing!', completed: true}) |
| | | .expect(200) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.body.should.have.property('_id'); |
| | | res.body.title.should.equal('LOVE endpoint/server side testing!'); |
| | | res.body.completed.should.equal(true); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 404 for valid mongo object id that does not exist', function(done){ |
| | | request(app) |
| | | .put('/api/todos/' + 'abcdef0123456789ABCDEF01') |
| | | .send({title: 'LOVE endpoint/server side testing!', completed: true}) |
| | | .expect(404) |
| | | .end(function(err) { |
| | | if (err) return done(err); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 400 for invalid object ids', function(done){ |
| | | request(app) |
| | | .put('/api/todos/' + 123) |
| | | .send({title: 'LOVE endpoint/server side testing!', completed: true}) |
| | | .expect(400) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.text.should.equal('not a valid mongo object id') |
| | | done(); |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | describe('DELETE /api/todos/:id', function() { |
| | | var todoId; |
| | | beforeEach(function createObjectToUpdate(done){ |
| | | request(app) |
| | | .post('/api/todos') |
| | | .send({title: 'learn about endpoint/server side testing', completed: false}) |
| | | .expect(201) |
| | | .expect('Content-Type', /json/) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | todoId = res.body._id; |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should delete the todo', function(done) { |
| | | request(app) |
| | | .delete('/api/todos/' + todoId) |
| | | .expect(204) |
| | | .end(function(err) { |
| | | if (err) return done(err); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 404 for valid mongo object id that does not exist', function(done){ |
| | | request(app) |
| | | .delete('/api/todos/' + 'abcdef0123456789ABCDEF01') |
| | | .expect(404) |
| | | .end(function(err) { |
| | | if (err) return done(err); |
| | | done(); |
| | | }); |
| | | }); |
| | | it('should return 400 for invalid object ids', function(done){ |
| | | request(app) |
| | | .delete('/api/todos/' + 123) |
| | | .send({title: 'LOVE endpoint/server side testing!', completed: true}) |
| | | .expect(400) |
| | | .end(function(err, res) { |
| | | if (err) return done(err); |
| | | res.text.should.equal('not a valid mongo object id') |
| | | done(); |
| | | }); |
| | | }); |
| | | }); |
New file |
| | |
| | | /** |
| | | * Main application routes |
| | | */ |
| | | |
| | | 'use strict'; |
| | | |
| | | var errors = require('./components/errors'); |
| | | var path = require('path'); |
| | | |
| | | module.exports = function(app) { |
| | | |
| | | // Insert routes below |
| | | app.use('/api/todos', require('./api/todo')); |
| | | |
| | | // All undefined asset or api routes should return a 404 |
| | | app.route('/:url(api|components|app|bower_components|assets)/*') |
| | | .get(errors[404]); |
| | | |
| | | // All other routes should redirect to the index.html |
| | | app.route('/*') |
| | | .get(function(req, res) { |
| | | res.sendFile(path.resolve(app.get('appPath') + '/index.html')); |
| | | }); |
| | | }; |
New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="en"> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <title>Page Not Found :(</title> |
| | | <style> |
| | | ::-moz-selection { |
| | | background: #b3d4fc; |
| | | text-shadow: none; |
| | | } |
| | | |
| | | ::selection { |
| | | background: #b3d4fc; |
| | | text-shadow: none; |
| | | } |
| | | |
| | | html { |
| | | padding: 30px 10px; |
| | | font-size: 20px; |
| | | line-height: 1.4; |
| | | color: #737373; |
| | | background: #f0f0f0; |
| | | -webkit-text-size-adjust: 100%; |
| | | -ms-text-size-adjust: 100%; |
| | | } |
| | | |
| | | html, |
| | | input { |
| | | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; |
| | | } |
| | | |
| | | body { |
| | | max-width: 500px; |
| | | _width: 500px; |
| | | padding: 30px 20px 50px; |
| | | border: 1px solid #b3b3b3; |
| | | border-radius: 4px; |
| | | margin: 0 auto; |
| | | box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff; |
| | | background: #fcfcfc; |
| | | } |
| | | |
| | | h1 { |
| | | margin: 0 10px; |
| | | font-size: 50px; |
| | | text-align: center; |
| | | } |
| | | |
| | | h1 span { |
| | | color: #bbb; |
| | | } |
| | | |
| | | h3 { |
| | | margin: 1.5em 0 0.5em; |
| | | } |
| | | |
| | | p { |
| | | margin: 1em 0; |
| | | } |
| | | |
| | | ul { |
| | | padding: 0 0 0 40px; |
| | | margin: 1em 0; |
| | | } |
| | | |
| | | .container { |
| | | max-width: 380px; |
| | | _width: 380px; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | /* google search */ |
| | | |
| | | #goog-fixurl ul { |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | } |
| | | |
| | | #goog-fixurl form { |
| | | margin: 0; |
| | | } |
| | | |
| | | #goog-wm-qt, |
| | | #goog-wm-sb { |
| | | border: 1px solid #bbb; |
| | | font-size: 16px; |
| | | line-height: normal; |
| | | vertical-align: top; |
| | | color: #444; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | #goog-wm-qt { |
| | | width: 220px; |
| | | height: 20px; |
| | | padding: 5px; |
| | | margin: 5px 10px 0 0; |
| | | box-shadow: inset 0 1px 1px #ccc; |
| | | } |
| | | |
| | | #goog-wm-sb { |
| | | display: inline-block; |
| | | height: 32px; |
| | | padding: 0 10px; |
| | | margin: 5px 0 0; |
| | | white-space: nowrap; |
| | | cursor: pointer; |
| | | background-color: #f5f5f5; |
| | | background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1); |
| | | background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1); |
| | | background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1); |
| | | background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1); |
| | | -webkit-appearance: none; |
| | | -moz-appearance: none; |
| | | appearance: none; |
| | | *overflow: visible; |
| | | *display: inline; |
| | | *zoom: 1; |
| | | } |
| | | |
| | | #goog-wm-sb:hover, |
| | | #goog-wm-sb:focus { |
| | | border-color: #aaa; |
| | | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); |
| | | background-color: #f8f8f8; |
| | | } |
| | | |
| | | #goog-wm-qt:hover, |
| | | #goog-wm-qt:focus { |
| | | border-color: #105cb6; |
| | | outline: 0; |
| | | color: #222; |
| | | } |
| | | |
| | | input::-moz-focus-inner { |
| | | padding: 0; |
| | | border: 0; |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <div class="container"> |
| | | <h1>Not found <span>:(</span></h1> |
| | | <p>Sorry, but the page you were trying to view does not exist.</p> |
| | | <p>It looks like this was the result of either:</p> |
| | | <ul> |
| | | <li>a mistyped address</li> |
| | | <li>an out-of-date link</li> |
| | | </ul> |
| | | <script> |
| | | var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host; |
| | | </script> |
| | | <script src="//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script> |
| | | </div> |
| | | </body> |
| | | </html> |
New file |
| | |
| | | /** |
| | | * Created by donal on 19/08/2016. |
| | | */ |
| | | var path = require('path'); |
| | | var srcDir = path.join(__dirname, '..', 'server'); |
| | | |
| | | // included as in the instructions here |
| | | // https://github.com/pghalliday/grunt-mocha-test#generating-coverage-reports |
| | | |
| | | require('blanket')({ |
| | | // Only files that match the pattern will be instrumented |
| | | pattern: srcDir |
| | | }); |
New file |
| | |
| | | var request = require('request'); |
| | | var benchrest = require('bench-rest'); |
| | | var grunt = require("grunt"); |
| | | var Q = require('q'); |
| | | |
| | | |
| | | // INFO ABOUT THE STATS |
| | | // stats.main.histogram.min - the minimum time any iteration took (milliseconds) |
| | | // stats.main.histogram.max - the maximum time any iteration took (milliseconds) |
| | | // stats.main.histogram.mean - the average time any iteration took (milliseconds) |
| | | // stats.main.histogram.p95 - the amount of time that 95% of all iterations completed within (milliseconds) |
| | | |
| | | var options = { |
| | | limit: 10, // concurrent connections |
| | | iterations: 10000 // number of iterations to perform |
| | | }; |
| | | var test = { |
| | | domain : 'http://localhost:9000', |
| | | dir : './reports/server/perf/', |
| | | route : '/api/todos/', |
| | | nfr : 60 |
| | | }; |
| | | var si = { |
| | | domain : 'http://localhost:9002', |
| | | dir : './reports/server/perf/', |
| | | route : '/api/todos/', |
| | | nfr : 60 |
| | | }; |
| | | var production = { |
| | | domain : 'http://localhost:80', |
| | | dir : './reports/server/perf/', |
| | | route : '/api/todos/', |
| | | nfr : 50 |
| | | }; |
| | | |
| | | var test_endpoint = function (flow, options) { |
| | | var wait = Q.defer(); |
| | | |
| | | benchrest(flow, options) |
| | | .on('error', function (err, ctxName) { |
| | | console.error('Failed in %s with err: ', ctxName, err); |
| | | }) |
| | | .on('end', function (stats, errorCount) { |
| | | console.log('\n\n###### ' +flow.filename +' - ' +flow.env.domain + flow.env.route); |
| | | console.log('Error Count', errorCount); |
| | | console.log('Stats', stats); |
| | | var mean_score = stats.main.histogram.mean; |
| | | var fs = require('fs-extra'); |
| | | var file = flow.env.dir + flow.filename + '-perf-score.csv'; |
| | | fs.outputFileSync(file, 'mean,max,mix,p95\n'+ stats.main.histogram.mean +',' |
| | | + stats.main.histogram.max +','+ stats.main.histogram.min +','+ stats.main.histogram.p95); |
| | | if (mean_score > flow.env.nfr){ |
| | | console.error('NFR EXCEEDED - ' +mean_score +' > '+flow.env.nfr); |
| | | wait.resolve(false); |
| | | } else { |
| | | wait.resolve(true); |
| | | } |
| | | }); |
| | | return wait.promise |
| | | }; |
| | | |
| | | |
| | | module.exports = function () { |
| | | grunt.task.registerTask('perf-test', 'Runs the performance tests against the target env', function(target, api) { |
| | | if (target === undefined || api === undefined){ |
| | | grunt.fail.fatal('Required param not set - use grunt perf-test\:\<target\>\:\<api\>'); |
| | | } else { |
| | | var done = this.async(); |
| | | var create = { |
| | | filename: 'create', |
| | | env: {}, |
| | | main: [{ |
| | | post: si.domain + si.route, |
| | | json: { |
| | | title: 'Run perf-test', |
| | | completed: false |
| | | } |
| | | }] |
| | | }; |
| | | |
| | | var show = { |
| | | filename: 'show', |
| | | env: {}, |
| | | main: [{ |
| | | get: si.domain + si.route |
| | | }] |
| | | }; |
| | | |
| | | if (target === 'si') { |
| | | show.env = si; |
| | | create.env = si; |
| | | } |
| | | else if (target === 'production') { |
| | | show.env = production; |
| | | create.env = production; |
| | | } |
| | | else if (target === 'test') { |
| | | show.env = test; |
| | | create.env = test; |
| | | } else { |
| | | grunt.fail.fatal('Invalid target - ' + target); |
| | | done(); |
| | | } |
| | | |
| | | grunt.log.ok("Perf tests running against " + target); |
| | | grunt.log.ok("This may take some time .... "); |
| | | |
| | | var all_tests = []; |
| | | |
| | | // console.log(create) |
| | | // console.log(show) |
| | | request(show.env.domain + show.env.route, function (error, response, body) { |
| | | if (error) { |
| | | grunt.log.error(error); |
| | | } else if(response.statusCode == 200) { |
| | | if (api === 'create'){ |
| | | all_tests.push(test_endpoint(create, options)); |
| | | } |
| | | else { |
| | | var mongoid = JSON.parse(body)[0]._id; |
| | | show.main[0].get = si.domain + si.route + mongoid; |
| | | all_tests.push(test_endpoint(show, options)); |
| | | } |
| | | |
| | | Q.all(all_tests).then(function (data) { |
| | | grunt.log.ok(data); |
| | | if (data.indexOf(false) > -1){ |
| | | grunt.fail.fatal('FAILURE - NFR NOT ACHIEVED'); |
| | | } else { |
| | | grunt.log.ok('SUCCESS - All NFR ACHIEVED'); |
| | | return done(); |
| | | } |
| | | }); |
| | | } else { |
| | | grunt.fail.fatal('FAILURE: something bad happened... there was no error from mongo but the response code was not 200') |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | }; |