diff --git a/.gitignore b/.gitignore
index 9e436b36f..78a6835b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,5 @@ demo
.idea
.DS_Store
release.txt
-fixtures/bower.json
-fixtures/package.json
\ No newline at end of file
+test/fixtures/bower.json
+test/fixtures/package.json
\ No newline at end of file
diff --git a/app/templates/Gruntfile.js b/app/templates/Gruntfile.js
index 998ca5620..a72f42dae 100644
--- a/app/templates/Gruntfile.js
+++ b/app/templates/Gruntfile.js
@@ -17,7 +17,8 @@ module.exports = function (grunt) {
cdnify: 'grunt-google-cdn',
protractor: 'grunt-protractor-runner',
injector: 'grunt-asset-injector',
- buildcontrol: 'grunt-build-control'
+ buildcontrol: 'grunt-build-control',
+ istanbul_check_coverage: 'grunt-mocha-istanbul'
});
// Time how long tasks take. Can help when optimizing build times
@@ -169,14 +170,14 @@ module.exports = function (grunt) {
},
src: [
'server/**/*.js',
- '!server/**/*.spec.js'
+ '!server/**/*.{spec,e2e}.js'
]
},
serverTest: {
options: {
jshintrc: 'server/.jshintrc-spec'
},
- src: ['server/**/*.spec.js']
+ src: ['server/**/*.{spec,e2e}.js']
},
all: [
'<%%= yeoman.client %>/{app,components}/**/*.js',
@@ -486,9 +487,60 @@ module.exports = function (grunt) {
mochaTest: {
options: {
- reporter: 'spec'
+ reporter: 'spec',
+ require: 'mocha.conf.js'
},
- src: ['server/**/*.spec.js']
+ unit: {
+ src: ['server/**/*.spec.js']
+ },
+ e2e: {
+ src: ['server/**/*.e2e.js']
+ }
+ },
+
+ mocha_istanbul: {
+ unit: {
+ options: {
+ excludes: [
+ '**/*.spec.js',
+ '**/*.mock.js',
+ '**/*.e2e.js'
+ ],
+ reporter: 'spec',
+ require: ['mocha.conf.js'],
+ mask: '**/*.spec.js',
+ coverageFolder: 'coverage/server/unit'
+ },
+ src: 'server'
+ },
+ e2e: {
+ options: {
+ excludes: [
+ '**/*.spec.js',
+ '**/*.mock.js',
+ '**/*.e2e.js'
+ ],
+ reporter: 'spec',
+ require: ['mocha.conf.js'],
+ mask: '**/*.e2e.js',
+ coverageFolder: 'coverage/server/e2e'
+ },
+ src: 'server'
+ }
+ },
+
+ istanbul_check_coverage: {
+ default: {
+ options: {
+ coverageFolder: 'coverage/**',
+ check: {
+ lines: 80,
+ statements: 80,
+ branches: 80,
+ functions: 80
+ }
+ }
+ }
},
protractor: {
@@ -764,12 +816,13 @@ module.exports = function (grunt) {
grunt.task.run(['serve']);
});
- grunt.registerTask('test', function(target) {
+ grunt.registerTask('test', function(target, option) {
if (target === 'server') {
return grunt.task.run([
'env:all',
'env:test',
- 'mochaTest'
+ 'mochaTest:unit',
+ 'mochaTest:e2e'
]);
}
@@ -804,6 +857,41 @@ module.exports = function (grunt) {
]);
}
+ else if (target === 'coverage') {
+
+ if (option === 'unit') {
+ return grunt.task.run([
+ 'env:all',
+ 'env:test',
+ 'mocha_istanbul:unit'
+ ]);
+ }
+
+ else if (option === 'e2e') {
+ return grunt.task.run([
+ 'env:all',
+ 'env:test',
+ 'mocha_istanbul:e2e'
+ ]);
+ }
+
+ else if (option === 'check') {
+ return grunt.task.run([
+ 'istanbul_check_coverage'
+ ]);
+ }
+
+ else {
+ return grunt.task.run([
+ 'env:all',
+ 'env:test',
+ 'mocha_istanbul',
+ 'istanbul_check_coverage'
+ ]);
+ }
+
+ }
+
else grunt.task.run([
'test:server',
'test:client'
diff --git a/app/templates/_package.json b/app/templates/_package.json
index e32849740..1912903fa 100644
--- a/app/templates/_package.json
+++ b/app/templates/_package.json
@@ -15,7 +15,8 @@
"lodash": "~2.4.1",<% if(filters.jade) { %>
"jade": "~1.2.0",<% } %><% if(filters.html) { %>
"ejs": "~0.8.4",<% } %><% if(filters.mongoose) { %>
- "mongoose": "~3.8.8",<% } %><% if(filters.auth) { %>
+ "mongoose": "~3.8.8",
+ "mongoose-bird": "~0.0.1",<% } %><% if(filters.auth) { %>
"jsonwebtoken": "^0.3.0",
"express-jwt": "^0.1.3",
"passport": "~0.2.0",
@@ -30,6 +31,7 @@
"socketio-jwt": "^2.0.2"<% } %>
},
"devDependencies": {
+ "chai-as-promised": "^4.1.1",
"grunt": "~0.4.4",
"grunt-autoprefixer": "~0.7.2",
"grunt-wiredep": "~1.8.0",
@@ -45,7 +47,7 @@
"grunt-contrib-watch": "~0.6.1",<% if(filters.coffee) { %>
"grunt-contrib-coffee": "^0.10.1",<% } %><% if(filters.jade) { %>
"grunt-contrib-jade": "^0.11.0",<% } %><% if(filters.less) { %>
- "grunt-contrib-less": "^0.11.0",<% } %>
+ "grunt-contrib-less": "^0.11.4",<% } %>
"grunt-google-cdn": "~0.4.0",
"grunt-newer": "~0.7.0",
"grunt-ng-annotate": "^0.2.3",
@@ -61,7 +63,8 @@
"grunt-asset-injector": "^0.1.0",
"grunt-karma": "~0.8.2",
"grunt-build-control": "DaftMonk/grunt-build-control",
- "grunt-mocha-test": "~0.10.2",<% if(filters.sass) { %>
+ "grunt-mocha-test": "~0.10.2",
+ "grunt-mocha-istanbul": "^2.0.0",<% if(filters.sass) { %>
"grunt-contrib-sass": "^0.7.3",<% } %><% if(filters.stylus) { %>
"grunt-contrib-stylus": "latest",<% } %>
"jit-grunt": "^0.5.0",
@@ -85,8 +88,9 @@
"karma-phantomjs-launcher": "~0.1.4",
"karma": "~0.12.9",
"karma-ng-html2js-preprocessor": "~0.1.0",
+ "proxyquire": "^1.0.1",
"supertest": "~0.11.0",
- "should": "~3.3.1"
+ "sinon-chai": "^2.5.0"
},
"engines": {
"node": ">=0.10.0"
diff --git a/app/templates/client/app/account(auth)/account(coffee).coffee b/app/templates/client/app/account(auth)/account(coffee).coffee
index 2b7b8b23b..088fb6840 100644
--- a/app/templates/client/app/account(auth)/account(coffee).coffee
+++ b/app/templates/client/app/account(auth)/account(coffee).coffee
@@ -7,6 +7,14 @@ angular.module '<%= scriptAppName %>'
templateUrl: 'app/account/login/login.html'
controller: 'LoginCtrl'
+ .when '/logout',
+ name: 'logout'
+ referrer: '/'
+ controller: ($location, $route, Auth) ->
+ referrer = $route.current.params.referrer or $route.current.referrer or "/"
+ Auth.logout()
+ $location.path referrer
+
.when '/signup',
templateUrl: 'app/account/signup/signup.html'
controller: 'SignupCtrl'
@@ -15,6 +23,10 @@ angular.module '<%= scriptAppName %>'
templateUrl: 'app/account/settings/settings.html'
controller: 'SettingsCtrl'
authenticate: true
+
+.run ($rootScope) ->
+ $rootScope.$on '$routeChangeStart', (event, next, current) ->
+ next.referrer = current.originalPath if next.name is "logout" and current and current.originalPath and not current.authenticate
<% } %><% if(filters.uirouter) { %>.config ($stateProvider) ->
$stateProvider
.state 'login',
@@ -22,6 +34,14 @@ angular.module '<%= scriptAppName %>'
templateUrl: 'app/account/login/login.html'
controller: 'LoginCtrl'
+ .state 'logout',
+ url: '/logout?referrer'
+ referrer: 'main'
+ controller: ($state, Auth) ->
+ referrer = $state.params.referrer or $state.current.referrer or "main"
+ Auth.logout()
+ $state.go referrer
+
.state 'signup',
url: '/signup'
templateUrl: 'app/account/signup/signup.html'
@@ -32,4 +52,8 @@ angular.module '<%= scriptAppName %>'
templateUrl: 'app/account/settings/settings.html'
controller: 'SettingsCtrl'
authenticate: true
-<% } %>
\ No newline at end of file
+
+.run ($rootScope) ->
+ $rootScope.$on '$stateChangeStart', (event, next, nextParams, current) ->
+ next.referrer = current.name if next.name is "logout" and current and current.name and not current.authenticate
+<% } %>
diff --git a/app/templates/client/app/account(auth)/account(js).js b/app/templates/client/app/account(auth)/account(js).js
index 0e30543a5..d31ff19d2 100644
--- a/app/templates/client/app/account(auth)/account(js).js
+++ b/app/templates/client/app/account(auth)/account(js).js
@@ -7,6 +7,17 @@ angular.module('<%= scriptAppName %>')
templateUrl: 'app/account/login/login.html',
controller: 'LoginCtrl'
})
+ .when('/logout', {
+ name: 'logout',
+ referrer: '/',
+ controller: function($location, $route, Auth) {
+ var referrer = $route.current.params.referrer ||
+ $route.current.referrer ||
+ '/';
+ Auth.logout();
+ $location.path(referrer);
+ }
+ })
.when('/signup', {
templateUrl: 'app/account/signup/signup.html',
controller: 'SignupCtrl'
@@ -16,6 +27,13 @@ angular.module('<%= scriptAppName %>')
controller: 'SettingsCtrl',
authenticate: true
});
+ })
+ .run(function($rootScope) {
+ $rootScope.$on('$routeChangeStart', function(event, next, current) {
+ if (next.name === 'logout' && current && current.originalPath && !current.authenticate) {
+ next.referrer = current.originalPath;
+ }
+ });
});<% } %><% if(filters.uirouter) { %>.config(function ($stateProvider) {
$stateProvider
.state('login', {
@@ -23,6 +41,17 @@ angular.module('<%= scriptAppName %>')
templateUrl: 'app/account/login/login.html',
controller: 'LoginCtrl'
})
+ .state('logout', {
+ url: '/logout?referrer',
+ referrer: 'main',
+ controller: function($state, Auth) {
+ var referrer = $state.params.referrer ||
+ $state.current.referrer ||
+ 'main';
+ Auth.logout();
+ $state.go(referrer);
+ }
+ })
.state('signup', {
url: '/signup',
templateUrl: 'app/account/signup/signup.html',
@@ -34,4 +63,11 @@ angular.module('<%= scriptAppName %>')
controller: 'SettingsCtrl',
authenticate: true
});
- });<% } %>
\ No newline at end of file
+ })
+ .run(function($rootScope) {
+ $rootScope.$on('$stateChangeStart', function(event, next, nextParams, current) {
+ if (next.name === 'logout' && current && current.name && !current.authenticate) {
+ next.referrer = current.name;
+ }
+ });
+ });<% } %>
diff --git a/app/templates/client/app/account(auth)/login/login(html).html b/app/templates/client/app/account(auth)/login/login(html).html
index 572f2e144..49a81b55d 100644
--- a/app/templates/client/app/account(auth)/login/login(html).html
+++ b/app/templates/client/app/account(auth)/login/login(html).html
@@ -37,7 +37,7 @@
Login
-
+ ui-sref="signup"<% } else { %>href="/signup"<% } %>>
Register
diff --git a/app/templates/client/app/account(auth)/login/login(jade).jade b/app/templates/client/app/account(auth)/login/login(jade).jade
index 4b13c0b13..686b1769e 100644
--- a/app/templates/client/app/account(auth)/login/login(jade).jade
+++ b/app/templates/client/app/account(auth)/login/login(jade).jade
@@ -34,7 +34,7 @@ div(ng-include='"components/navbar/navbar.html"')
button.btn.btn-inverse.btn-lg.btn-login(type='submit')
| Login
= ' '
- a.btn.btn-default.btn-lg.btn-register(href='/signup')
+ a.btn.btn-default.btn-lg.btn-register(<% if(filters.uirouter) { %>ui-sref='signup'<% } else { %>href='/signup'<% } %>)
| Register
<% if(filters.oauth) {%>
hr
diff --git a/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee b/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee
index 3f90c25d7..036191f93 100644
--- a/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee
+++ b/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee
@@ -1,7 +1,7 @@
'use strict'
angular.module '<%= scriptAppName %>'
-.controller 'LoginCtrl', ($scope, Auth, $location<% if(filters.oauth) {%>, $window<% } %>) ->
+.controller 'LoginCtrl', ($scope, Auth<% if(filters.ngroute) { %>, $location<% } %><% if(filters.uirouter) { %>, $state<% } %><% if(filters.oauth) {%>, $window<% } %>) ->
$scope.user = {}
$scope.errors = {}
$scope.login = (form) ->
@@ -14,7 +14,7 @@ angular.module '<%= scriptAppName %>'
password: $scope.user.password
.then ->
- $location.path '/'
+ <% if(filters.ngroute) { %>$location.path '/'<% } %><% if(filters.uirouter) { %>$state.go 'main'<% } %>
.catch (err) ->
$scope.errors.other = err.message
diff --git a/app/templates/client/app/account(auth)/login/login.controller(js).js b/app/templates/client/app/account(auth)/login/login.controller(js).js
index 7b13da384..e2c5dcaa4 100644
--- a/app/templates/client/app/account(auth)/login/login.controller(js).js
+++ b/app/templates/client/app/account(auth)/login/login.controller(js).js
@@ -1,7 +1,7 @@
'use strict';
angular.module('<%= scriptAppName %>')
- .controller('LoginCtrl', function ($scope, Auth, $location<% if (filters.oauth) { %>, $window<% } %>) {
+ .controller('LoginCtrl', function ($scope, Auth<% if(filters.ngroute) { %>, $location<% } %><% if(filters.uirouter) { %>, $state<% } %><% if (filters.oauth) { %>, $window<% } %>) {
$scope.user = {};
$scope.errors = {};
@@ -15,7 +15,7 @@ angular.module('<%= scriptAppName %>')
})
.then( function() {
// Logged in, redirect to home
- $location.path('/');
+ <% if(filters.ngroute) { %>$location.path('/');<% } %><% if(filters.uirouter) { %>$state.go('main');<% } %>
})
.catch( function(err) {
$scope.errors.other = err.message;
diff --git a/app/templates/client/app/account(auth)/signup/signup(html).html b/app/templates/client/app/account(auth)/signup/signup(html).html
index 59faed568..28d6c39ab 100644
--- a/app/templates/client/app/account(auth)/signup/signup(html).html
+++ b/app/templates/client/app/account(auth)/signup/signup(html).html
@@ -58,7 +58,7 @@ Sign up
-
+ ui-sref="login"<% } else { %>href="/login"<% } %>>
Login
diff --git a/app/templates/client/app/account(auth)/signup/signup(jade).jade b/app/templates/client/app/account(auth)/signup/signup(jade).jade
index 43815a21c..7e21b101c 100644
--- a/app/templates/client/app/account(auth)/signup/signup(jade).jade
+++ b/app/templates/client/app/account(auth)/signup/signup(jade).jade
@@ -36,7 +36,7 @@ div(ng-include='"components/navbar/navbar.html"')
button.btn.btn-inverse.btn-lg.btn-login(type='submit')
| Sign up
= ' '
- a.btn.btn-default.btn-lg.btn-register(href='/login')
+ a.btn.btn-default.btn-lg.btn-register(<% if(filters.uirouter) { %>ui-sref='login'<% } else { %>href='/login'<% } %>)
| Login
<% if(filters.oauth) {%>
diff --git a/app/templates/client/app/account(auth)/signup/signup.controller(coffee).coffee b/app/templates/client/app/account(auth)/signup/signup.controller(coffee).coffee
index 1b9c9696f..ac240faa8 100644
--- a/app/templates/client/app/account(auth)/signup/signup.controller(coffee).coffee
+++ b/app/templates/client/app/account(auth)/signup/signup.controller(coffee).coffee
@@ -1,7 +1,7 @@
'use strict'
angular.module '<%= scriptAppName %>'
-.controller 'SignupCtrl', ($scope, Auth, $location<% if(filters.oauth) {%>, $window<% } %>) ->
+.controller 'SignupCtrl', ($scope, Auth<% if(filters.ngroute) { %>, $location<% } %><% if(filters.uirouter) { %>, $state<% } %><% if(filters.oauth) {%>, $window<% } %>) ->
$scope.user = {}
$scope.errors = {}
$scope.register = (form) ->
@@ -15,7 +15,7 @@ angular.module '<%= scriptAppName %>'
password: $scope.user.password
.then ->
- $location.path '/'
+ <% if(filters.ngroute) { %>$location.path '/'<% } %><% if(filters.uirouter) { %>$state.go 'main'<% } %>
.catch (err) ->
err = err.data
diff --git a/app/templates/client/app/account(auth)/signup/signup.controller(js).js b/app/templates/client/app/account(auth)/signup/signup.controller(js).js
index 7d6ba3d38..10685079d 100644
--- a/app/templates/client/app/account(auth)/signup/signup.controller(js).js
+++ b/app/templates/client/app/account(auth)/signup/signup.controller(js).js
@@ -1,7 +1,7 @@
'use strict';
angular.module('<%= scriptAppName %>')
- .controller('SignupCtrl', function ($scope, Auth, $location<% if (filters.oauth) { %>, $window<% } %>) {
+ .controller('SignupCtrl', function ($scope, Auth<% if(filters.ngroute) { %>, $location<% } %><% if(filters.uirouter) { %>, $state<% } %><% if (filters.oauth) { %>, $window<% } %>) {
$scope.user = {};
$scope.errors = {};
@@ -16,7 +16,7 @@ angular.module('<%= scriptAppName %>')
})
.then( function() {
// Account created, redirect to home
- $location.path('/');
+ <% if(filters.ngroute) { %>$location.path('/');<% } %><% if(filters.uirouter) { %>$state.go('main');<% } %>
})
.catch( function(err) {
err = err.data;
diff --git a/app/templates/client/app/app(coffee).coffee b/app/templates/client/app/app(coffee).coffee
index ea9ae3c95..cb6d0c5b1 100644
--- a/app/templates/client/app/app(coffee).coffee
+++ b/app/templates/client/app/app(coffee).coffee
@@ -15,8 +15,9 @@ angular.module '<%= scriptAppName %>', [<%= angularModules %>]
$locationProvider.html5Mode true<% if(filters.auth) { %>
$httpProvider.interceptors.push 'authInterceptor'<% } %>
<% } %><% if(filters.auth) { %>
-.factory 'authInterceptor', ($rootScope, $q, $cookieStore, $location) ->
- # Add authorization token to headers
+.factory 'authInterceptor', ($rootScope, $q, $cookieStore<% if(filters.ngroute) { %>, $location<% } if(filters.uirouter) { %>, $injector<% } %>) ->
+ <% if(filters.uirouter) { %>state = null
+ <% } %># Add authorization token to headers
request: (config) ->
config.headers = config.headers or {}
config.headers.Authorization = 'Bearer ' + $cookieStore.get 'token' if $cookieStore.get 'token'
@@ -25,15 +26,15 @@ angular.module '<%= scriptAppName %>', [<%= angularModules %>]
# Intercept 401s and redirect you to login
responseError: (response) ->
if response.status is 401
- $location.path '/login'
+ <% if(filters.ngroute) { %>$location.path '/login'<% } if(filters.uirouter) { %>(state || state = $injector.get '$state').go 'login'<% } %>
# remove any stale tokens
$cookieStore.remove 'token'
$q.reject response
-.run ($rootScope, $location, Auth) ->
+.run ($rootScope<% if(filters.ngroute) { %>, $location<% } %><% if(filters.uirouter) { %>, $state<% } %>, Auth) ->
# Redirect to login if route requires auth and you're not logged in
$rootScope.$on <% if(filters.ngroute) { %>'$routeChangeStart'<% } %><% if(filters.uirouter) { %>'$stateChangeStart'<% } %>, (event, next) ->
- Auth.isLoggedInAsync (loggedIn) ->
- $location.path "/login" if next.authenticate and not loggedIn
-<% } %>
\ No newline at end of file
+ Auth.isLoggedIn (loggedIn) ->
+ <% if(filters.ngroute) { %>$location.path '/login'<% } %><% if(filters.uirouter) { %>$state.go 'login'<% } %> if next.authenticate and not loggedIn
+<% } %>
diff --git a/app/templates/client/app/app(js).js b/app/templates/client/app/app(js).js
index eef485d7c..35b8ad327 100644
--- a/app/templates/client/app/app(js).js
+++ b/app/templates/client/app/app(js).js
@@ -9,16 +9,17 @@ angular.module('<%= scriptAppName %>', [<%= angularModules %>])
$locationProvider.html5Mode(true);<% if(filters.auth) { %>
$httpProvider.interceptors.push('authInterceptor');<% } %>
- })<% } %><% if(filters.uirouter) { %>.config(function ($stateProvider, $urlRouterProvider, $locationProvider<% if(filters.auth) { %>, $httpProvider<% } %>) {
+ })<% } if(filters.uirouter) { %>.config(function ($stateProvider, $urlRouterProvider, $locationProvider<% if(filters.auth) { %>, $httpProvider<% } %>) {
$urlRouterProvider
.otherwise('/');
$locationProvider.html5Mode(true);<% if(filters.auth) { %>
$httpProvider.interceptors.push('authInterceptor');<% } %>
- })<% } %><% if(filters.auth) { %>
+ })<% } if(filters.auth) { %>
- .factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
- return {
+ .factory('authInterceptor', function ($rootScope, $q, $cookieStore<% if(filters.ngroute) { %>, $location<% } if(filters.uirouter) { %>, $injector<% } %>) {
+ <% if(filters.uirouter) { %>var state;
+ <% } %>return {
// Add authorization token to headers
request: function (config) {
config.headers = config.headers || {};
@@ -31,7 +32,7 @@ angular.module('<%= scriptAppName %>', [<%= angularModules %>])
// Intercept 401s and redirect you to login
responseError: function(response) {
if(response.status === 401) {
- $location.path('/login');
+ <% if(filters.ngroute) { %>$location.path('/login');<% } if(filters.uirouter) { %>(state || (state = $injector.get('$state'))).go('login');<% } %>
// remove any stale tokens
$cookieStore.remove('token');
return $q.reject(response);
@@ -43,13 +44,13 @@ angular.module('<%= scriptAppName %>', [<%= angularModules %>])
};
})
- .run(function ($rootScope, $location, Auth) {
+ .run(function ($rootScope<% if(filters.ngroute) { %>, $location<% } if(filters.uirouter) { %>, $state<% } %>, Auth) {
// Redirect to login if route requires auth and you're not logged in
$rootScope.$on(<% if(filters.ngroute) { %>'$routeChangeStart'<% } %><% if(filters.uirouter) { %>'$stateChangeStart'<% } %>, function (event, next) {
- Auth.isLoggedInAsync(function(loggedIn) {
+ Auth.isLoggedIn(function(loggedIn) {
if (next.authenticate && !loggedIn) {
- $location.path('/login');
+ <% if(filters.ngroute) { %>$location.path('/login');<% } if(filters.uirouter) { %>$state.go('login');<% } %>
}
});
});
- })<% } %>;
\ No newline at end of file
+ })<% } %>;
diff --git a/app/templates/client/app/main/main.controller.spec(coffee).coffee b/app/templates/client/app/main/main.controller.spec(coffee).coffee
index efe9b39a6..f974a081d 100644
--- a/app/templates/client/app/main/main.controller.spec(coffee).coffee
+++ b/app/templates/client/app/main/main.controller.spec(coffee).coffee
@@ -3,15 +3,17 @@
describe 'Controller: MainCtrl', ->
# load the controller's module
- beforeEach module '<%= scriptAppName %>' <% if(filters.socketio) {%>
+ beforeEach module '<%= scriptAppName %>' <% if(filters.uirouter) {%>
+ beforeEach module 'stateMock' <% } %><% if(filters.socketio) {%>
beforeEach module 'socketMock' <% } %>
MainCtrl = undefined
- scope = undefined
+ scope = undefined<% if(filters.uirouter) {%>
+ state = undefined<% } %>
$httpBackend = undefined
# Initialize the controller and a mock scope
- beforeEach inject (_$httpBackend_, $controller, $rootScope) ->
+ beforeEach inject (_$httpBackend_, $controller, $rootScope<% if(filters.uirouter) {%>, $state<% } %>) ->
$httpBackend = _$httpBackend_
$httpBackend.expectGET('/api/things').respond [
'HTML5 Boilerplate'
@@ -19,10 +21,11 @@ describe 'Controller: MainCtrl', ->
'Karma'
'Express'
]
- scope = $rootScope.$new()
+ scope = $rootScope.$new()<% if(filters.uirouter) {%>
+ state = $state<% } %>
MainCtrl = $controller 'MainCtrl',
$scope: scope
it 'should attach a list of things to the scope', ->
$httpBackend.flush()
- expect(scope.awesomeThings.length).toBe 4
\ No newline at end of file
+ expect(scope.awesomeThings.length).toBe 4
diff --git a/app/templates/client/app/main/main.controller.spec(js).js b/app/templates/client/app/main/main.controller.spec(js).js
index 373e9db08..21b8aba70 100644
--- a/app/templates/client/app/main/main.controller.spec(js).js
+++ b/app/templates/client/app/main/main.controller.spec(js).js
@@ -3,20 +3,23 @@
describe('Controller: MainCtrl', function () {
// load the controller's module
- beforeEach(module('<%= scriptAppName %>'));<% if(filters.socketio) {%>
+ beforeEach(module('<%= scriptAppName %>'));<% if(filters.uirouter) {%>
+ beforeEach(module('stateMock'));<% } %><% if(filters.socketio) {%>
beforeEach(module('socketMock'));<% } %>
var MainCtrl,
- scope,
+ scope,<% if(filters.uirouter) {%>
+ state,<% } %>
$httpBackend;
// Initialize the controller and a mock scope
- beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) {
+ beforeEach(inject(function (_$httpBackend_, $controller, $rootScope<% if(filters.uirouter) {%>, $state<% } %>) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('/api/things')
.respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']);
- scope = $rootScope.$new();
+ scope = $rootScope.$new();<% if(filters.uirouter) {%>
+ state = $state;<% } %>
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
diff --git a/app/templates/client/components/auth(auth)/auth.service(coffee).coffee b/app/templates/client/components/auth(auth)/auth.service(coffee).coffee
index ac503ed0b..1d4b29544 100644
--- a/app/templates/client/components/auth(auth)/auth.service(coffee).coffee
+++ b/app/templates/client/components/auth(auth)/auth.service(coffee).coffee
@@ -1,40 +1,35 @@
'use strict'
angular.module '<%= scriptAppName %>'
-.factory 'Auth', ($location, $rootScope, $http, User, $cookieStore, $q) ->
+.factory 'Auth', ($http, User, $cookieStore, $q) ->
currentUser = if $cookieStore.get 'token' then User.get() else {}
###
Authenticate user and save token
@param {Object} user - login info
- @param {Function} callback - optional
+ @param {Function} callback - optional, function(error)
@return {Promise}
###
login: (user, callback) ->
- deferred = $q.defer()
$http.post '/auth/local',
email: user.email
password: user.password
- .success (data) ->
- $cookieStore.put 'token', data.token
+ .then (res) ->
+ $cookieStore.put 'token', res.data.token
currentUser = User.get()
- deferred.resolve data
callback?()
+ res.data
- .error (err) =>
+ , (err) =>
@logout()
- deferred.reject err
- callback? err
-
- deferred.promise
+ callback? err.data
+ $q.reject err.data
###
Delete access token and user info
-
- @param {Function}
###
logout: ->
$cookieStore.remove 'token'
@@ -46,7 +41,7 @@ angular.module '<%= scriptAppName %>'
Create a new user
@param {Object} user - user info
- @param {Function} callback - optional
+ @param {Function} callback - optional, function(error, user)
@return {Promise}
###
createUser: (user, callback) ->
@@ -54,7 +49,7 @@ angular.module '<%= scriptAppName %>'
(data) ->
$cookieStore.put 'token', data.token
currentUser = User.get()
- callback? user
+ callback? null, user
, (err) =>
@logout()
@@ -68,7 +63,7 @@ angular.module '<%= scriptAppName %>'
@param {String} oldPassword
@param {String} newPassword
- @param {Function} callback - optional
+ @param {Function} callback - optional, function(error, user)
@return {Promise}
###
changePassword: (oldPassword, newPassword, callback) ->
@@ -79,7 +74,7 @@ angular.module '<%= scriptAppName %>'
newPassword: newPassword
, (user) ->
- callback? user
+ callback? null, user
, (err) ->
callback? err
@@ -88,45 +83,61 @@ angular.module '<%= scriptAppName %>'
###
- Gets all available info on authenticated user
+ Gets all available info on a user
+ (synchronous|asynchronous)
- @return {Object} user
+ @param {Function|*} callback - optional, funciton(user)
+ @return {Object|Promise}
###
- getCurrentUser: ->
- currentUser
+ getCurrentUser: (callback) ->
+ return currentUser if arguments.length is 0
+ value = if (currentUser.hasOwnProperty("$promise")) then currentUser.$promise else currentUser
+ $q.when value
- ###
- Check if a user is logged in synchronously
+ .then (user) ->
+ callback? user
+ user
- @return {Boolean}
- ###
- isLoggedIn: ->
- currentUser.hasOwnProperty 'role'
+ , ->
+ callback? {}
+ {}
###
- Waits for currentUser to resolve before checking if user is logged in
+ Check if a user is logged in
+ (synchronous|asynchronous)
+
+ @param {Function|*} callback - optional, function(is)
+ @return {Bool|Promise}
###
- isLoggedInAsync: (callback) ->
- if currentUser.hasOwnProperty '$promise'
- currentUser.$promise.then ->
- callback? true
- return
- .catch ->
- callback? false
- return
+ isLoggedIn: (callback) ->
+ return currentUser.hasOwnProperty("role") if arguments.length is 0
+
+ @getCurrentUser null
+
+ .then (user) ->
+ is_ = user.hasOwnProperty("role")
+ callback? is_
+ is_
- else
- callback? currentUser.hasOwnProperty 'role'
###
Check if a user is an admin
+ (synchronous|asynchronous)
- @return {Boolean}
+ @param {Function|*} callback - optional, function(is)
+ @return {Bool|Promise}
###
- isAdmin: ->
- currentUser.role is 'admin'
+ isAdmin: (callback) ->
+ return currentUser.role is "admin" if arguments.length is 0
+
+ @getCurrentUser null
+
+ .then (user) ->
+ is_ = user.role is "admin"
+ callback? is_
+ is_
###
diff --git a/app/templates/client/components/auth(auth)/auth.service(js).js b/app/templates/client/components/auth(auth)/auth.service(js).js
index 9afb12da9..5baf0e0b4 100644
--- a/app/templates/client/components/auth(auth)/auth.service(js).js
+++ b/app/templates/client/components/auth(auth)/auth.service(js).js
@@ -1,9 +1,20 @@
'use strict';
angular.module('<%= scriptAppName %>')
- .factory('Auth', function Auth($location, $rootScope, $http, User, $cookieStore, $q) {
- var currentUser = {};
- if($cookieStore.get('token')) {
+ .factory('Auth', function Auth($http, User, $cookieStore, $q) {
+ /**
+ * Return a callback or noop function
+ *
+ * @param {Function|*} cb - a 'potential' function
+ * @return {Function}
+ */
+ var safeCb = function(cb) {
+ return (angular.isFunction(cb)) ? cb : angular.noop;
+ },
+
+ currentUser = {};
+
+ if ($cookieStore.get('token')) {
currentUser = User.get();
}
@@ -13,36 +24,28 @@ angular.module('<%= scriptAppName %>')
* Authenticate user and save token
*
* @param {Object} user - login info
- * @param {Function} callback - optional
+ * @param {Function} callback - optional, function(error)
* @return {Promise}
*/
login: function(user, callback) {
- var cb = callback || angular.noop;
- var deferred = $q.defer();
-
- $http.post('/auth/local', {
+ return $http.post('/auth/local', {
email: user.email,
password: user.password
- }).
- success(function(data) {
- $cookieStore.put('token', data.token);
+ })
+ .then(function(res) {
+ $cookieStore.put('token', res.data.token);
currentUser = User.get();
- deferred.resolve(data);
- return cb();
- }).
- error(function(err) {
+ safeCb(callback)();
+ return res.data;
+ }, function(err) {
this.logout();
- deferred.reject(err);
- return cb(err);
+ safeCb(callback)(err.data);
+ return $q.reject(err.data);
}.bind(this));
-
- return deferred.promise;
},
/**
* Delete access token and user info
- *
- * @param {Function}
*/
logout: function() {
$cookieStore.remove('token');
@@ -53,21 +56,19 @@ angular.module('<%= scriptAppName %>')
* Create a new user
*
* @param {Object} user - user info
- * @param {Function} callback - optional
+ * @param {Function} callback - optional, function(error, user)
* @return {Promise}
*/
createUser: function(user, callback) {
- var cb = callback || angular.noop;
-
return User.save(user,
function(data) {
$cookieStore.put('token', data.token);
currentUser = User.get();
- return cb(user);
+ return safeCb(callback)(null, user);
},
function(err) {
this.logout();
- return cb(err);
+ return safeCb(callback)(err);
}.bind(this)).$promise;
},
@@ -76,68 +77,87 @@ angular.module('<%= scriptAppName %>')
*
* @param {String} oldPassword
* @param {String} newPassword
- * @param {Function} callback - optional
+ * @param {Function} callback - optional, function(error, user)
* @return {Promise}
*/
changePassword: function(oldPassword, newPassword, callback) {
- var cb = callback || angular.noop;
-
return User.changePassword({ id: currentUser._id }, {
oldPassword: oldPassword,
newPassword: newPassword
}, function(user) {
- return cb(user);
+ return safeCb(callback)(null, user);
}, function(err) {
- return cb(err);
+ return safeCb(callback)(err);
}).$promise;
},
/**
- * Gets all available info on authenticated user
+ * Gets all available info on a user
+ * (synchronous|asynchronous)
*
- * @return {Object} user
+ * @param {Function|*} callback - optional, funciton(user)
+ * @return {Object|Promise}
*/
- getCurrentUser: function() {
- return currentUser;
+ getCurrentUser: function(callback) {
+ if (arguments.length === 0) {
+ return currentUser;
+ }
+
+ var value = (currentUser.hasOwnProperty('$promise')) ? currentUser.$promise : currentUser;
+ return $q.when(value)
+ .then(function(user) {
+ safeCb(callback)(user);
+ return user;
+ }, function() {
+ safeCb(callback)({});
+ return {};
+ });
},
/**
* Check if a user is logged in
+ * (synchronous|asynchronous)
*
- * @return {Boolean}
+ * @param {Function|*} callback - optional, function(is)
+ * @return {Bool|Promise}
*/
- isLoggedIn: function() {
- return currentUser.hasOwnProperty('role');
- },
+ isLoggedIn: function(callback) {
+ if (arguments.length === 0) {
+ return currentUser.hasOwnProperty('role');
+ }
- /**
- * Waits for currentUser to resolve before checking if user is logged in
- */
- isLoggedInAsync: function(cb) {
- if(currentUser.hasOwnProperty('$promise')) {
- currentUser.$promise.then(function() {
- cb(true);
- }).catch(function() {
- cb(false);
+ return this.getCurrentUser(null)
+ .then(function(user) {
+ var is = user.hasOwnProperty('role');
+ safeCb(callback)(is);
+ return is;
});
- } else if(currentUser.hasOwnProperty('role')) {
- cb(true);
- } else {
- cb(false);
- }
},
- /**
- * Check if a user is an admin
- *
- * @return {Boolean}
- */
- isAdmin: function() {
- return currentUser.role === 'admin';
+ /**
+ * Check if a user is an admin
+ * (synchronous|asynchronous)
+ *
+ * @param {Function|*} callback - optional, function(is)
+ * @return {Bool|Promise}
+ */
+ isAdmin: function(callback) {
+ if (arguments.length === 0) {
+ return currentUser.role === 'admin';
+ }
+
+ return this.getCurrentUser(null)
+ .then(function(user) {
+ var is = user.role === 'admin';
+ safeCb(callback)(is);
+ return is;
+ });
},
/**
* Get auth token
+ *
+ * @return {String} - a token string used for authenticating
*/
getToken: function() {
return $cookieStore.get('token');
diff --git a/app/templates/client/components/navbar/navbar(html).html b/app/templates/client/components/navbar/navbar(html).html
index 71f8606dd..a93839562 100644
--- a/app/templates/client/components/navbar/navbar(html).html
+++ b/app/templates/client/components/navbar/navbar(html).html
@@ -11,18 +11,18 @@
<% if(filters.auth) { %>
<% } %>
diff --git a/app/templates/client/components/navbar/navbar(jade).jade b/app/templates/client/components/navbar/navbar(jade).jade
index 2b17f29c3..7863f4e0a 100644
--- a/app/templates/client/components/navbar/navbar(jade).jade
+++ b/app/templates/client/components/navbar/navbar(jade).jade
@@ -10,25 +10,25 @@ div.navbar.navbar-default.navbar-static-top(ng-controller='NavbarCtrl')
div#navbar-main.navbar-collapse.collapse(collapse='isCollapsed')
ul.nav.navbar-nav
- li(ng-repeat='item in menu', ng-class='{active: isActive(item.link)}')
- a(ng-href='{{item.link}}') {{item.title}}<% if(filters.auth) { %>
+ li(ng-repeat='item in menu', <% if(filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive(item.link)}'<% } %>)
+ a(<% if(filters.uirouter) { %>ui-sref='{{item.state}}'<% } else { %>ng-href='{{item.link}}'<% } %>) {{item.title}}<% if(filters.auth) { %>
- li(ng-show='isAdmin()', ng-class='{active: isActive("/admin")}')
- a(href='/admin') Admin<% } %><% if(filters.auth) { %>
+ li(ng-show='isAdmin()', <% if(filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive("/admin")}'<% } %>)
+ a(<% if(filters.uirouter) { %>ui-sref='admin'<% } else { %>href='/admin'<% } %>) Admin
ul.nav.navbar-nav.navbar-right
- li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/signup")}')
- a(href='/signup') Sign up
+ li(ng-hide='isLoggedIn()', <% if(filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive("/signup")}'<% } %>)
+ a(<% if(filters.uirouter) { %>ui-sref='signup'<% } else { %>href='/signup'<% } %>) Sign up
- li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/login")}')
- a(href='/login') Login
+ li(ng-hide='isLoggedIn()', <% if(filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive("/login")}'<% } %>)
+ a(<% if(filters.uirouter) { %>ui-sref='login'<% } else { %>href='/login'<% } %>) Login
li(ng-show='isLoggedIn()')
p.navbar-text Hello {{ getCurrentUser().name }}
- li(ng-show='isLoggedIn()', ng-class='{active: isActive("/settings")}')
- a(href='/settings')
+ li(ng-show='isLoggedIn()', <% if(filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive("/settings")}'<% } %>)
+ a(<% if(filters.uirouter) { %>ui-sref='settings'<% } else { %>href='/settings'<% } %>)
span.glyphicon.glyphicon-cog
- li(ng-show='isLoggedIn()', ng-class='{active: isActive("/logout")}')
- a(href='', ng-click='logout()') Logout<% } %>
\ No newline at end of file
+ li(ng-show='isLoggedIn()')
+ a(<% if(filters.uirouter) { %>ui-sref='logout'<% } else { %>href='/logout'<% } %>) Logout<% } %>
\ No newline at end of file
diff --git a/app/templates/client/components/navbar/navbar.controller(coffee).coffee b/app/templates/client/components/navbar/navbar.controller(coffee).coffee
index d3804c5eb..121437cf1 100644
--- a/app/templates/client/components/navbar/navbar.controller(coffee).coffee
+++ b/app/templates/client/components/navbar/navbar.controller(coffee).coffee
@@ -1,19 +1,15 @@
'use strict'
angular.module '<%= scriptAppName %>'
-.controller 'NavbarCtrl', ($scope, $location<% if(filters.auth) {%>, Auth<% } %>) ->
+.controller 'NavbarCtrl', ($scope<% if(!filters.uirouter) { %>, $location<% } %><% if(filters.auth) {%>, Auth<% } %>) ->
$scope.menu = [
title: 'Home'
- link: '/'
+ <% if(filters.uirouter) { %>state: 'main'<% } else { %>link: '/'<% } %>
]
$scope.isCollapsed = true<% if(filters.auth) {%>
$scope.isLoggedIn = Auth.isLoggedIn
$scope.isAdmin = Auth.isAdmin
- $scope.getCurrentUser = Auth.getCurrentUser
-
- $scope.logout = ->
- Auth.logout()
- $location.path '/login'<% } %>
+ $scope.getCurrentUser = Auth.getCurrentUser<% } %><% if(!filters.uirouter) { %>
$scope.isActive = (route) ->
- route is $location.path()
\ No newline at end of file
+ route is $location.path()<% } %>
\ No newline at end of file
diff --git a/app/templates/client/components/navbar/navbar.controller(js).js b/app/templates/client/components/navbar/navbar.controller(js).js
index 4ce9dbcb5..2428ac15b 100644
--- a/app/templates/client/components/navbar/navbar.controller(js).js
+++ b/app/templates/client/components/navbar/navbar.controller(js).js
@@ -1,23 +1,18 @@
'use strict';
angular.module('<%= scriptAppName %>')
- .controller('NavbarCtrl', function ($scope, $location<% if(filters.auth) {%>, Auth<% } %>) {
+ .controller('NavbarCtrl', function ($scope<% if(!filters.uirouter) { %>, $location<% } %><% if(filters.auth) {%>, Auth<% } %>) {
$scope.menu = [{
'title': 'Home',
- 'link': '/'
+ <% if(filters.uirouter) { %>'state': 'main'<% } else { %>'link': '/'<% } %>
}];
$scope.isCollapsed = true;<% if(filters.auth) {%>
$scope.isLoggedIn = Auth.isLoggedIn;
$scope.isAdmin = Auth.isAdmin;
- $scope.getCurrentUser = Auth.getCurrentUser;
-
- $scope.logout = function() {
- Auth.logout();
- $location.path('/login');
- };<% } %>
+ $scope.getCurrentUser = Auth.getCurrentUser;<% } %><% if(!filters.uirouter) { %>
$scope.isActive = function(route) {
return route === $location.path();
- };
- });
\ No newline at end of file
+ };<% } %>
+ });
diff --git a/app/templates/client/components/socket(socketio)/socket.mock.js b/app/templates/client/components/socket(socketio)/socket.mock(js).js
similarity index 100%
rename from app/templates/client/components/socket(socketio)/socket.mock.js
rename to app/templates/client/components/socket(socketio)/socket.mock(js).js
diff --git a/app/templates/client/components/socket(socketio)/socket.service.js b/app/templates/client/components/socket(socketio)/socket.service(js).js
similarity index 100%
rename from app/templates/client/components/socket(socketio)/socket.service.js
rename to app/templates/client/components/socket(socketio)/socket.service(js).js
diff --git a/app/templates/client/components/ui-router(uirouter)/ui-router.mock(coffee).coffee b/app/templates/client/components/ui-router(uirouter)/ui-router.mock(coffee).coffee
new file mode 100644
index 000000000..ff3937c35
--- /dev/null
+++ b/app/templates/client/components/ui-router(uirouter)/ui-router.mock(coffee).coffee
@@ -0,0 +1,26 @@
+'use strict'
+
+angular.module 'stateMock', []
+angular.module('stateMock').service '$state', ($q) ->
+ @expectedTransitions = []
+
+ @transitionTo = (stateName) ->
+ if @expectedTransitions.length > 0
+ expectedState = @expectedTransitions.shift()
+ throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName) if expectedState isnt stateName
+ else
+ throw Error('No more transitions were expected! Tried to transition to ' + stateName)
+ console.log 'Mock transition to: ' + stateName
+ deferred = $q.defer()
+ promise = deferred.promise
+ deferred.resolve()
+ promise
+
+ @go = @transitionTo
+
+ @expectTransitionTo = (stateName) ->
+ @expectedTransitions.push stateName
+
+ @ensureAllTransitionsHappened = ->
+ throw Error('Not all transitions happened!') if @expectedTransitions.length > 0
+ @
diff --git a/app/templates/client/components/ui-router(uirouter)/ui-router.mock(js).js b/app/templates/client/components/ui-router(uirouter)/ui-router.mock(js).js
new file mode 100644
index 000000000..a5a1bf413
--- /dev/null
+++ b/app/templates/client/components/ui-router(uirouter)/ui-router.mock(js).js
@@ -0,0 +1,34 @@
+'use strict';
+
+angular.module('stateMock', []);
+angular.module('stateMock').service('$state', function($q) {
+ this.expectedTransitions = [];
+
+ this.transitionTo = function(stateName) {
+ if (this.expectedTransitions.length > 0) {
+ var expectedState = this.expectedTransitions.shift();
+ if (expectedState !== stateName) {
+ throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName);
+ }
+ } else {
+ throw Error('No more transitions were expected! Tried to transition to ' + stateName);
+ }
+ console.log('Mock transition to: ' + stateName);
+ var deferred = $q.defer();
+ var promise = deferred.promise;
+ deferred.resolve();
+ return promise;
+ };
+
+ this.go = this.transitionTo;
+
+ this.expectTransitionTo = function(stateName) {
+ this.expectedTransitions.push(stateName);
+ };
+
+ this.ensureAllTransitionsHappened = function() {
+ if (this.expectedTransitions.length > 0) {
+ throw Error('Not all transitions happened!');
+ }
+ };
+});
diff --git a/app/templates/mocha.conf.js b/app/templates/mocha.conf.js
new file mode 100644
index 000000000..497d43b2c
--- /dev/null
+++ b/app/templates/mocha.conf.js
@@ -0,0 +1,14 @@
+'use strict';
+
+var chai = require('chai');
+var sinon = require('sinon');
+var sinonChai = require('sinon-chai');
+var chaiAsPromised = require('chai-as-promised');
+
+global.expect = chai.expect;
+global.assert = chai.assert;
+global.sinon = sinon;
+
+chai.should();
+chai.use(sinonChai);
+chai.use(chaiAsPromised);
diff --git a/app/templates/server/.jshintrc b/app/templates/server/.jshintrc
index d7b958e7c..42fea558a 100644
--- a/app/templates/server/.jshintrc
+++ b/app/templates/server/.jshintrc
@@ -1,4 +1,5 @@
{
+ "expr": true,
"node": true,
"esnext": true,
"bitwise": true,
diff --git a/app/templates/server/.jshintrc-spec b/app/templates/server/.jshintrc-spec
index b6b55cbf9..b9390c374 100644
--- a/app/templates/server/.jshintrc-spec
+++ b/app/templates/server/.jshintrc-spec
@@ -6,6 +6,9 @@
"before": true,
"beforeEach": true,
"after": true,
- "afterEach": true
+ "afterEach": true,
+ "expect": true,
+ "assert": true,
+ "sinon": true
}
}
diff --git a/app/templates/server/api/thing/index.js b/app/templates/server/api/thing/index.js
index 242ed5901..e77e80c5b 100644
--- a/app/templates/server/api/thing/index.js
+++ b/app/templates/server/api/thing/index.js
@@ -12,4 +12,4 @@ router.put('/:id', controller.update);
router.patch('/:id', controller.update);
router.delete('/:id', controller.destroy);<% } %>
-module.exports = router;
\ No newline at end of file
+module.exports = router;
diff --git a/app/templates/server/api/thing/index.spec.js b/app/templates/server/api/thing/index.spec.js
new file mode 100644
index 000000000..e62feff60
--- /dev/null
+++ b/app/templates/server/api/thing/index.spec.js
@@ -0,0 +1,85 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+ /* thing.controller stub */
+var thingCtrl = {
+ index: 'thingCtrl.index'<% if(filters.mongoose) { %>,
+ show: 'thingCtrl.show',
+ create: 'thingCtrl.create',
+ update: 'thingCtrl.update',
+ destroy: 'thingCtrl.destroy'<% } %>
+ },
+ /* express.Router().router stub */
+ router = {
+ get: sinon.spy()<% if(filters.mongoose) { %>,
+ put: sinon.spy(),
+ patch: sinon.spy(),
+ post: sinon.spy(),
+ delete: sinon.spy()<% } %>
+ },
+ /* stubbed thing router */
+ index = proxyquire('./index.js', {
+ 'express': {
+ Router: function() {
+ return router;
+ }
+ },
+ './thing.controller': thingCtrl
+ });
+
+describe('Thing API Router:', function() {
+
+ it('should return an express router instance', function() {
+ index.should.equal(router);
+ });
+
+ describe('GET /api/things', function() {
+
+ it('should route to thing.controller.index', function() {
+ router.get.withArgs('/', 'thingCtrl.index').should.have.been.calledOnce;
+ });
+
+ });<% if(filters.mongoose) { %>
+
+ describe('GET /api/things/:id', function() {
+
+ it('should route to thing.controller.show', function() {
+ router.get.withArgs('/:id', 'thingCtrl.show').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('POST /api/things', function() {
+
+ it('should route to thing.controller.create', function() {
+ router.post.withArgs('/', 'thingCtrl.create').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('PUT /api/things/:id', function() {
+
+ it('should route to thing.controller.update', function() {
+ router.put.withArgs('/:id', 'thingCtrl.update').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('PATCH /api/things/:id', function() {
+
+ it('should route to thing.controller.update', function() {
+ router.patch.withArgs('/:id', 'thingCtrl.update').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('DELETE /api/things/:id', function() {
+
+ it('should route to thing.controller.destroy', function() {
+ router.delete.withArgs('/:id', 'thingCtrl.destroy').should.have.been.calledOnce;
+ });
+
+ });<% } %>
+
+});
diff --git a/app/templates/server/api/thing/thing.controller.js b/app/templates/server/api/thing/thing.controller.js
index ba84d6fc9..4d5abca05 100644
--- a/app/templates/server/api/thing/thing.controller.js
+++ b/app/templates/server/api/thing/thing.controller.js
@@ -7,10 +7,57 @@
* DELETE /things/:id -> destroy
*/
-'use strict';
+'use strict';<% if (filters.mongoose) { %>
-var _ = require('lodash');<% if (filters.mongoose) { %>
-var Thing = require('./thing.model');<% } %>
+var _ = require('lodash');
+var Thing = require('./thing.model');
+
+function handleError(res, statusCode){
+ statusCode = statusCode || 500;
+ return function(err){
+ res.send(statusCode, err);
+ };
+}
+
+function responseWithResult(res, statusCode){
+ statusCode = statusCode || 200;
+ return function(entity){
+ if(entity){
+ return res.json(statusCode, entity);
+ }
+ };
+}
+
+function handleEntityNotFound(res){
+ return function(entity){
+ if(!entity){
+ res.send(404);
+ return null;
+ }
+ return entity;
+ };
+}
+
+function saveUpdates(updates){
+ return function(entity){
+ var updated = _.merge(entity, updates);
+ return updated.saveAsync()
+ .then(function () {
+ return updated;
+ });
+ };
+}
+
+function removeEntity(res){
+ return function (entity) {
+ if(entity){
+ return entity.removeAsync()
+ .then(function() {
+ return res.send(204);
+ });
+ }
+ };
+}<% } %>
// Get list of things
exports.index = function(req, res) {<% if (!filters.mongoose) { %>
@@ -34,56 +81,43 @@ exports.index = function(req, res) {<% if (!filters.mongoose) { %>
name : 'Deployment Ready',
info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators'
}
- ]);<% } %><% if (filters.mongoose) { %>
- Thing.find(function (err, things) {
- if(err) { return handleError(res, err); }
- return res.json(200, things);
- });<% } %>
+ ]);<% } if (filters.mongoose) { %>
+ Thing.findAsync()
+ .then(responseWithResult(res))
+ .catch(handleError(res));<% } %>
};<% if (filters.mongoose) { %>
// Get a single thing
exports.show = function(req, res) {
- Thing.findById(req.params.id, function (err, thing) {
- if(err) { return handleError(res, err); }
- if(!thing) { return res.send(404); }
- return res.json(thing);
- });
+ Thing.findByIdAsync(req.params.id)
+ .then(handleEntityNotFound(res))
+ .then(responseWithResult(res))
+ .catch(handleError(res));
};
// Creates a new thing in the DB.
exports.create = function(req, res) {
- Thing.create(req.body, function(err, thing) {
- if(err) { return handleError(res, err); }
- return res.json(201, thing);
- });
+ Thing.createAsync(req.body)
+ .then(responseWithResult(res, 201))
+ .catch(handleError(res));
};
// Updates an existing thing in the DB.
exports.update = function(req, res) {
- if(req.body._id) { delete req.body._id; }
- Thing.findById(req.params.id, function (err, thing) {
- if (err) { return handleError(res, err); }
- if(!thing) { return res.send(404); }
- var updated = _.merge(thing, req.body);
- updated.save(function (err) {
- if (err) { return handleError(res, err); }
- return res.json(200, thing);
- });
- });
+ if(req.body._id) {
+ delete req.body._id;
+ }
+ Thing.findByIdAsync(req.params.id)
+ .then(handleEntityNotFound(res))
+ .then(saveUpdates(req.body))
+ .then(responseWithResult(res))
+ .catch(handleError(res));
};
// Deletes a thing from the DB.
exports.destroy = function(req, res) {
- Thing.findById(req.params.id, function (err, thing) {
- if(err) { return handleError(res, err); }
- if(!thing) { return res.send(404); }
- thing.remove(function(err) {
- if(err) { return handleError(res, err); }
- return res.send(204);
- });
- });
-};
-
-function handleError(res, err) {
- return res.send(500, err);
-}<% } %>
\ No newline at end of file
+ Thing.findByIdAsync(req.params.id)
+ .then(handleEntityNotFound(res))
+ .then(removeEntity(res))
+ .catch(handleError(res));
+};<% } %>
diff --git a/app/templates/server/api/thing/thing.e2e.js b/app/templates/server/api/thing/thing.e2e.js
new file mode 100644
index 000000000..1140df7e9
--- /dev/null
+++ b/app/templates/server/api/thing/thing.e2e.js
@@ -0,0 +1,135 @@
+'use strict';
+
+var app = require('../../app');
+var request = require('supertest');<% if(filters.mongoose) { %>
+
+var newThing;<% } %>
+
+describe('Thing API:', function() {
+
+ describe('GET /api/things', function() {
+ var things;
+
+ beforeEach(function(done) {
+ request(app)
+ .get('/api/things')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ things = res.body;
+ done();
+ });
+ });
+
+ it('should respond with JSON array', function() {
+ things.should.be.instanceOf(Array);
+ });
+
+ });<% if(filters.mongoose) { %>
+
+ describe('POST /api/things', function() {
+ beforeEach(function(done) {
+ request(app)
+ .post('/api/things')
+ .send({
+ name: 'New Thing',
+ info: 'This is the brand new thing!!!'
+ })
+ .expect(201)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ newThing = res.body;
+ done();
+ });
+ });
+
+ it('should respond with the newly created thing', function() {
+ newThing.name.should.equal('New Thing');
+ newThing.info.should.equal('This is the brand new thing!!!');
+ });
+
+ });
+
+ describe('GET /api/things/:id', function() {
+ var thing;
+
+ beforeEach(function(done) {
+ request(app)
+ .get('/api/things/' + newThing._id)
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ thing = res.body;
+ done();
+ });
+ });
+
+ afterEach(function() {
+ thing = {};
+ });
+
+ it('should respond with the requested thing', function() {
+ thing.name.should.equal('New Thing');
+ thing.info.should.equal('This is the brand new thing!!!');
+ });
+
+ });
+
+ describe('PUT /api/things/:id', function() {
+ var updatedThing
+
+ beforeEach(function(done) {
+ request(app)
+ .put('/api/things/' + newThing._id)
+ .send({
+ name: 'Updated Thing',
+ info: 'This is the updated thing!!!'
+ })
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ updatedThing = res.body;
+ done();
+ });
+ });
+
+ afterEach(function() {
+ updatedThing = {};
+ });
+
+ it('should respond with the updated thing', function() {
+ updatedThing.name.should.equal('Updated Thing');
+ updatedThing.info.should.equal('This is the updated thing!!!');
+ });
+
+ });
+
+ describe('DELETE /api/things/:id', function() {
+
+ it('should respond with 204 on successful removal', function(done) {
+ request(app)
+ .delete('/api/things/' + newThing._id)
+ .expect(204)
+ .end(function(err, res) {
+ if (err) return done(err);
+ done();
+ });
+ });
+
+ it('should respond with 404 when thing does not exsist', function(done) {
+ request(app)
+ .delete('/api/things/' + newThing._id)
+ .expect(404)
+ .end(function(err, res) {
+ if (err) return done(err);
+ done();
+ });
+ });
+
+ });<% } %>
+
+});
diff --git a/app/templates/server/api/thing/thing.model(mongoose).js b/app/templates/server/api/thing/thing.model(mongoose).js
index ed857cd3b..a44bc710e 100644
--- a/app/templates/server/api/thing/thing.model(mongoose).js
+++ b/app/templates/server/api/thing/thing.model(mongoose).js
@@ -1,6 +1,6 @@
'use strict';
-var mongoose = require('mongoose'),
+var mongoose = require('mongoose-bird')(),
Schema = mongoose.Schema;
var ThingSchema = new Schema({
@@ -9,4 +9,4 @@ var ThingSchema = new Schema({
active: Boolean
});
-module.exports = mongoose.model('Thing', ThingSchema);
\ No newline at end of file
+module.exports = mongoose.model('Thing', ThingSchema);
diff --git a/app/templates/server/api/thing/thing.socket(socketio).js b/app/templates/server/api/thing/thing.socket(socketio).js
index 79d327695..dbf3e2fe7 100644
--- a/app/templates/server/api/thing/thing.socket(socketio).js
+++ b/app/templates/server/api/thing/thing.socket(socketio).js
@@ -21,4 +21,4 @@ function onSave(socket, doc, cb) {
function onRemove(socket, doc, cb) {
socket.emit('thing:remove', doc);
-}
\ No newline at end of file
+}
diff --git a/app/templates/server/api/thing/thing.spec.js b/app/templates/server/api/thing/thing.spec.js
deleted file mode 100644
index 17c8c6cd0..000000000
--- a/app/templates/server/api/thing/thing.spec.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-var should = require('should');
-var app = require('../../app');
-var request = require('supertest');
-
-describe('GET /api/things', function() {
-
- it('should respond with JSON array', function(done) {
- request(app)
- .get('/api/things')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(function(err, res) {
- if (err) return done(err);
- res.body.should.be.instanceof(Array);
- done();
- });
- });
-});
diff --git a/app/templates/server/api/user(auth)/index.js b/app/templates/server/api/user(auth)/index.js
index 48567e485..be6fd3af3 100644
--- a/app/templates/server/api/user(auth)/index.js
+++ b/app/templates/server/api/user(auth)/index.js
@@ -2,7 +2,6 @@
var express = require('express');
var controller = require('./user.controller');
-var config = require('../../config/environment');
var auth = require('../../auth/auth.service');
var router = express.Router();
diff --git a/app/templates/server/api/user(auth)/index.spec.js b/app/templates/server/api/user(auth)/index.spec.js
new file mode 100644
index 000000000..30b786fb3
--- /dev/null
+++ b/app/templates/server/api/user(auth)/index.spec.js
@@ -0,0 +1,95 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+ /* user.controller stub */
+var userCtrl = {
+ index: 'userCtrl.index',
+ destroy: 'userCtrl.destroy',
+ me: 'userCtrl.me',
+ changePassword: 'userCtrl.changePassword',
+ show: 'userCtrl.show',
+ create: 'userCtrl.create'
+ },
+ /* auth.service stub */
+ authService = {
+ isAuthenticated: function() {
+ return 'authService.isAuthenticated';
+ },
+ hasRole: function(role) {
+ return 'authService.hasRole.' + role;
+ }
+ },
+ /* express.Router().router stub */
+ router = {
+ get: sinon.spy(),
+ put: sinon.spy(),
+ post: sinon.spy(),
+ delete: sinon.spy()
+ },
+ /* stubbed user router */
+ index = proxyquire('./index', {
+ 'express': {
+ Router: function() {
+ return router;
+ }
+ },
+ './user.controller': userCtrl,
+ '../../auth/auth.service': authService
+ });
+
+describe('User API Router:', function() {
+
+ it('should return an express router instance', function() {
+ index.should.equal(router);
+ });
+
+ describe('GET /api/users', function() {
+
+ it('should verify admin role and route to user.controller.index', function() {
+ router.get.withArgs('/', 'authService.hasRole.admin', 'userCtrl.index').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('DELETE /api/users/:id', function() {
+
+ it('should verify admin role and route to user.controller.destroy', function() {
+ router.delete.withArgs('/:id', 'authService.hasRole.admin', 'userCtrl.destroy').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('GET /api/users/me', function() {
+
+ it('should be authenticated and route to user.controller.me', function() {
+ router.get.withArgs('/me', 'authService.isAuthenticated', 'userCtrl.me').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('PUT /api/users/:id/password', function() {
+
+ it('should be authenticated and route to user.controller.changePassword', function() {
+ router.put.withArgs('/:id/password', 'authService.isAuthenticated', 'userCtrl.changePassword').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('GET /api/users/:id', function() {
+
+ it('should be authenticated and route to user.controller.show', function() {
+ router.get.withArgs('/:id', 'authService.isAuthenticated', 'userCtrl.show').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('POST /api/users', function() {
+
+ it('should route to user.controller.create', function() {
+ router.post.withArgs('/', 'userCtrl.create').should.have.been.calledOnce;
+ });
+
+ });
+
+});
diff --git a/app/templates/server/api/user(auth)/user.controller.js b/app/templates/server/api/user(auth)/user.controller.js
index f4cd10c29..410aa8afd 100644
--- a/app/templates/server/api/user(auth)/user.controller.js
+++ b/app/templates/server/api/user(auth)/user.controller.js
@@ -5,19 +5,37 @@ var passport = require('passport');
var config = require('../../config/environment');
var jwt = require('jsonwebtoken');
-var validationError = function(res, err) {
- return res.json(422, err);
+var validationError = function(res, statusCode) {
+ statusCode = statusCode || 422;
+ return function(err){
+ res.json(statusCode, err);
+ };
};
+function handleError(res, statusCode){
+ statusCode = statusCode || 500;
+ return function(err){
+ res.send(statusCode, err);
+ };
+}
+
+function respondWith(res, statusCode){
+ statusCode = statusCode || 200;
+ return function(){
+ res.send(statusCode);
+ };
+}
+
/**
* Get list of users
* restriction: 'admin'
*/
exports.index = function(req, res) {
- User.find({}, '-salt -hashedPassword', function (err, users) {
- if(err) return res.send(500, err);
- res.json(200, users);
- });
+ User.findAsync({}, '-salt -password')
+ .then(function (users) {
+ res.json(200, users);
+ })
+ .catch(handleError(res));
};
/**
@@ -27,11 +45,12 @@ exports.create = function (req, res, next) {
var newUser = new User(req.body);
newUser.provider = 'local';
newUser.role = 'user';
- newUser.save(function(err, user) {
- if (err) return validationError(res, err);
- var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60*5 });
- res.json({ token: token });
- });
+ newUser.saveAsync()
+ .spread(function (user, affectedRows) {
+ var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60*5 });
+ res.json({ token: token });
+ })
+ .catch(validationError(res));
};
/**
@@ -40,11 +59,16 @@ exports.create = function (req, res, next) {
exports.show = function (req, res, next) {
var userId = req.params.id;
- User.findById(userId, function (err, user) {
- if (err) return next(err);
- if (!user) return res.send(401);
- res.json(user.profile);
- });
+ User.findByIdAsync(userId)
+ .then(function (user) {
+ if(!user) {
+ return res.send(401);
+ }
+ res.json(user.profile);
+ })
+ .catch(function(err){
+ return next(err);
+ });
};
/**
@@ -52,10 +76,9 @@ exports.show = function (req, res, next) {
* restriction: 'admin'
*/
exports.destroy = function(req, res) {
- User.findByIdAndRemove(req.params.id, function(err, user) {
- if(err) return res.send(500, err);
- return res.send(204);
- });
+ User.findByIdAndRemoveAsync(req.params.id)
+ .then(respondWith(res, 204))
+ .catch(handleError(res));
};
/**
@@ -66,17 +89,17 @@ exports.changePassword = function(req, res, next) {
var oldPass = String(req.body.oldPassword);
var newPass = String(req.body.newPassword);
- User.findById(userId, function (err, user) {
- if(user.authenticate(oldPass)) {
- user.password = newPass;
- user.save(function(err) {
- if (err) return validationError(res, err);
- res.send(200);
- });
- } else {
- res.send(403);
- }
- });
+ User.findByIdAsync(userId)
+ .then(function(user) {
+ if(user.authenticate(oldPass)) {
+ user.password = newPass;
+ return user.saveAsync()
+ .then(respondWith(res, 200))
+ .catch(validationError(res));
+ } else {
+ return res.send(403);
+ }
+ });
};
/**
@@ -84,13 +107,13 @@ exports.changePassword = function(req, res, next) {
*/
exports.me = function(req, res, next) {
var userId = req.user._id;
- User.findOne({
- _id: userId
- }, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt
- if (err) return next(err);
- if (!user) return res.json(401);
- res.json(user);
- });
+
+ User.findOneAsync({ _id: userId }, '-salt -password')
+ .then(function(user) { // don't ever give out the password or salt
+ if (!user) { return res.json(401); }
+ res.json(user);
+ })
+ .catch(function(err){ return next(err); });
};
/**
diff --git a/app/templates/server/api/user(auth)/user.e2e.js b/app/templates/server/api/user(auth)/user.e2e.js
new file mode 100644
index 000000000..917acedd9
--- /dev/null
+++ b/app/templates/server/api/user(auth)/user.e2e.js
@@ -0,0 +1,68 @@
+'use strict';
+
+var app = require('../../app');
+var User = require('./user.model');
+var request = require('supertest');
+
+describe('User API:', function() {
+ var user;
+
+ // Clear users before testing
+ before(function(done) {
+ User.remove(function() {
+ user = new User({
+ name: 'Fake User',
+ email: 'test@test.com',
+ password: 'password'
+ });
+
+ user.save(function(err) {
+ if (err) return done(err);
+ done();
+ });
+ });
+ });
+
+ // Clear users after testing
+ after(function() {
+ return User.remove().exec();
+ });
+
+ describe('GET /api/users/me', function() {
+ var token;
+
+ before(function(done) {
+ request(app)
+ .post('/auth/local')
+ .send({
+ email: 'test@test.com',
+ password: 'password'
+ })
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ token = res.body.token;
+ done();
+ });
+ });
+
+ it('should respond with a user profile when authenticated', function(done) {
+ request(app)
+ .get('/api/users/me')
+ .set('authorization', 'Bearer ' + token)
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ res.body._id.should.equal(user._id.toString());
+ done();
+ });
+ });
+
+ it('should respond with a 401 when not authenticated', function(done) {
+ request(app)
+ .get('/api/users/me')
+ .expect(401)
+ .end(done);
+ });
+ });
+});
diff --git a/app/templates/server/api/user(auth)/user.model.js b/app/templates/server/api/user(auth)/user.model.js
index cc8d59263..b90386886 100644
--- a/app/templates/server/api/user(auth)/user.model.js
+++ b/app/templates/server/api/user(auth)/user.model.js
@@ -1,6 +1,6 @@
'use strict';
-var mongoose = require('mongoose');
+var mongoose = require('mongoose-bird')();
var Schema = mongoose.Schema;
var crypto = require('crypto');<% if(filters.oauth) { %>
var authTypes = ['github', 'twitter', 'facebook', 'google'];<% } %>
@@ -12,7 +12,7 @@ var UserSchema = new Schema({
type: String,
default: 'user'
},
- hashedPassword: String,
+ password: String,
provider: String,
salt: String<% if (filters.oauth) { %>,<% if (filters.facebookAuth) { %>
facebook: {},<% } %><% if (filters.twitterAuth) { %>
@@ -24,16 +24,6 @@ var UserSchema = new Schema({
/**
* Virtuals
*/
-UserSchema
- .virtual('password')
- .set(function(password) {
- this._password = password;
- this.salt = this.makeSalt();
- this.hashedPassword = this.encryptPassword(password);
- })
- .get(function() {
- return this._password;
- });
// Public profile information
UserSchema
@@ -63,16 +53,20 @@ UserSchema
UserSchema
.path('email')
.validate(function(email) {<% if (filters.oauth) { %>
- if (authTypes.indexOf(this.provider) !== -1) return true;<% } %>
+ if (authTypes.indexOf(this.provider) !== -1){
+ return true;
+ } <% } %>
return email.length;
}, 'Email cannot be blank');
// Validate empty password
UserSchema
- .path('hashedPassword')
- .validate(function(hashedPassword) {<% if (filters.oauth) { %>
- if (authTypes.indexOf(this.provider) !== -1) return true;<% } %>
- return hashedPassword.length;
+ .path('password')
+ .validate(function(password) {<% if (filters.oauth) { %>
+ if (authTypes.indexOf(this.provider) !== -1){
+ return true;
+ } <% } %>
+ return password.length;
}, 'Password cannot be blank');
// Validate email is not taken
@@ -80,14 +74,19 @@ UserSchema
.path('email')
.validate(function(value, respond) {
var self = this;
- this.constructor.findOne({email: value}, function(err, user) {
- if(err) throw err;
- if(user) {
- if(self.id === user.id) return respond(true);
- return respond(false);
- }
- respond(true);
- });
+ return this.constructor.findOneAsync({email: value})
+ .then(function(user) {
+ if(user) {
+ if(self.id === user.id) {
+ return respond(true);
+ }
+ return respond(false);
+ }
+ return respond(true);
+ })
+ .catch(function(err){
+ throw err;
+ });
}, 'The specified email address is already in use.');
var validatePresenceOf = function(value) {
@@ -99,12 +98,26 @@ var validatePresenceOf = function(value) {
*/
UserSchema
.pre('save', function(next) {
- if (!this.isNew) return next();
-
- if (!validatePresenceOf(this.hashedPassword)<% if (filters.oauth) { %> && authTypes.indexOf(this.provider) === -1<% } %>)
- next(new Error('Invalid password'));
- else
+ // Handle new/update passwords
+ if (this.password) {
+ if (!validatePresenceOf(this.password)<% if (filters.oauth) { %> && authTypes.indexOf(this.provider) === -1<% } %>)
+ next(new Error('Invalid password'));
+
+ // Make salt with a callback
+ var _this = this;
+ this.makeSalt(function(saltErr, salt) {
+ if (saltErr) next(saltErr);
+ _this.salt = salt;
+ // Async hash
+ _this.encryptPassword(_this.password, function(encryptErr, hashedPassword) {
+ if (encryptErr) next(encryptErr);
+ _this.password = hashedPassword;
+ next();
+ });
+ });
+ } else {
next();
+ }
});
/**
@@ -115,34 +128,82 @@ UserSchema.methods = {
* Authenticate - check if the passwords are the same
*
* @param {String} plainText
+ * @callback {callback} Optional callback
* @return {Boolean}
* @api public
*/
- authenticate: function(plainText) {
- return this.encryptPassword(plainText) === this.hashedPassword;
+ authenticate: function(password, callback) {
+ if (!callback)
+ return this.password === this.encryptPassword(password);
+
+ var _this = this;
+ this.encryptPassword(password, function(err, pwdGen) {
+ if (err) callback(err);
+
+ if (_this.password === pwdGen) {
+ callback(null, true);
+ } else {
+ callback(null, false);
+ }
+ });
},
/**
* Make salt
*
+ * @param {Number} byteSize Optional salt byte size, default to 16
+ * @callback {callback} Optional callback
* @return {String}
* @api public
*/
- makeSalt: function() {
- return crypto.randomBytes(16).toString('base64');
+ makeSalt: function(byteSize, callback) {
+ var defaultByteSize = 16;
+
+ if (typeof arguments[0] === 'function') {
+ callback = arguments[0];
+ byteSize = defaultByteSize;
+ } else if (typeof arguments[1] === 'function') {
+ callback = arguments[1];
+ }
+
+ if (!byteSize) {
+ byteSize = defaultByteSize;
+ }
+
+ if (!callback) {
+ return crypto.randomBytes(byteSize).toString('base64');
+ }
+
+ return crypto.randomBytes(byteSize, function(err, salt) {
+ if (err) callback(err);
+ return callback(null, salt.toString('base64'));
+ });
},
/**
* Encrypt password
*
* @param {String} password
+ * @callback {callback} Optional callback
* @return {String}
* @api public
*/
- encryptPassword: function(password) {
- if (!password || !this.salt) return '';
+ encryptPassword: function(password, callback) {
+ if (!password || !this.salt) {
+ return null;
+ }
+
+ var defaultIterations = 10000;
+ var defaultKeyLength = 64;
var salt = new Buffer(this.salt, 'base64');
- return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64');
+
+ if (!callback)
+ return crypto.pbkdf2Sync(password, salt, defaultIterations, defaultKeyLength).toString('base64');
+
+ return crypto.pbkdf2(password, salt, defaultIterations, defaultKeyLength, function(err, key) {
+ if (err) callback(err);
+ return callback(null, key.toString('base64'));
+ });
}
};
diff --git a/app/templates/server/api/user(auth)/user.model.spec.js b/app/templates/server/api/user(auth)/user.model.spec.js
index 257c95b7c..fe51ea2ea 100644
--- a/app/templates/server/api/user(auth)/user.model.spec.js
+++ b/app/templates/server/api/user(auth)/user.model.spec.js
@@ -1,6 +1,5 @@
'use strict';
-var should = require('should');
var app = require('../../app');
var User = require('./user.model');
@@ -12,49 +11,38 @@ var user = new User({
});
describe('User Model', function() {
- before(function(done) {
+ before(function() {
// Clear users before testing
- User.remove().exec().then(function() {
- done();
- });
+ return User.remove().exec();
});
- afterEach(function(done) {
- User.remove().exec().then(function() {
- done();
- });
+ afterEach(function() {
+ return User.remove().exec();
});
- it('should begin with no users', function(done) {
- User.find({}, function(err, users) {
- users.should.have.length(0);
- done();
- });
+ it('should begin with no users', function() {
+ return User.findAsync({})
+ .should.eventually.have.length(0);
});
- it('should fail when saving a duplicate user', function(done) {
- user.save(function() {
- var userDup = new User(user);
- userDup.save(function(err) {
- should.exist(err);
- done();
- });
- });
+ it('should fail when saving a duplicate user', function() {
+ return user.saveAsync()
+ .then(function() {
+ var userDup = new User(user);
+ return userDup.saveAsync();
+ }).should.be.rejected;
});
- it('should fail when saving without an email', function(done) {
+ it('should fail when saving without an email', function() {
user.email = '';
- user.save(function(err) {
- should.exist(err);
- done();
- });
+ return user.saveAsync().should.be.rejected;
});
it("should authenticate user if password is valid", function() {
- return user.authenticate('password').should.be.true;
+ user.authenticate('password').should.be.true;
});
it("should not authenticate user if password is invalid", function() {
- return user.authenticate('blah').should.not.be.true;
+ user.authenticate('blah').should.not.be.true;
});
});
diff --git a/app/templates/server/app.js b/app/templates/server/app.js
index 08b942f43..5369e94ef 100644
--- a/app/templates/server/app.js
+++ b/app/templates/server/app.js
@@ -8,7 +8,7 @@
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var express = require('express');<% if (filters.mongoose) { %>
-var mongoose = require('mongoose');<% } %>
+var mongoose = require('mongoose-bird')();<% } %>
var config = require('./config/environment');
<% if (filters.mongoose) { %>
// Connect to database
@@ -34,4 +34,4 @@ server.listen(config.port, config.ip, function () {
});
// Expose app
-exports = module.exports = app;
\ No newline at end of file
+exports = module.exports = app;
diff --git a/app/templates/server/auth(auth)/auth.service.js b/app/templates/server/auth(auth)/auth.service.js
index 38ec34302..101dcc5c5 100644
--- a/app/templates/server/auth(auth)/auth.service.js
+++ b/app/templates/server/auth(auth)/auth.service.js
@@ -1,6 +1,6 @@
'use strict';
-var mongoose = require('mongoose');
+var mongoose = require('mongoose-bird')();
var passport = require('passport');
var config = require('../config/environment');
var jwt = require('jsonwebtoken');
@@ -25,13 +25,17 @@ function isAuthenticated() {
})
// Attach user to request
.use(function(req, res, next) {
- User.findById(req.user._id, function (err, user) {
- if (err) return next(err);
- if (!user) return res.send(401);
-
- req.user = user;
- next();
- });
+ User.findByIdAsync(req.user._id)
+ .then(function (user) {
+ if (!user) {
+ return res.send(401);
+ }
+ req.user = user;
+ next();
+ })
+ .catch(function(err){
+ return next(err);
+ });
});
}
@@ -39,7 +43,9 @@ function isAuthenticated() {
* Checks if the user role meets the minimum requirements of the route
*/
function hasRole(roleRequired) {
- if (!roleRequired) throw new Error('Required role needs to be set');
+ if (!roleRequired) {
+ throw new Error('Required role needs to be set');
+ }
return compose()
.use(isAuthenticated())
@@ -64,7 +70,9 @@ function signToken(id) {
* Set token cookie directly for oAuth strategies
*/
function setTokenCookie(req, res) {
- if (!req.user) return res.json(404, { message: 'Something went wrong, please try again.'});
+ if (!req.user) {
+ return res.json(404, { message: 'Something went wrong, please try again.'});
+ }
var token = signToken(req.user._id, req.user.role);
res.cookie('token', JSON.stringify(token));
res.redirect('/');
@@ -73,4 +81,4 @@ function setTokenCookie(req, res) {
exports.isAuthenticated = isAuthenticated;
exports.hasRole = hasRole;
exports.signToken = signToken;
-exports.setTokenCookie = setTokenCookie;
\ No newline at end of file
+exports.setTokenCookie = setTokenCookie;
diff --git a/app/templates/server/auth(auth)/local/passport.js b/app/templates/server/auth(auth)/local/passport.js
index ac82b42a2..4221f8786 100644
--- a/app/templates/server/auth(auth)/local/passport.js
+++ b/app/templates/server/auth(auth)/local/passport.js
@@ -15,11 +15,15 @@ exports.setup = function (User, config) {
if (!user) {
return done(null, false, { message: 'This email is not registered.' });
}
- if (!user.authenticate(password)) {
- return done(null, false, { message: 'This password is not correct.' });
- }
- return done(null, user);
+ user.authenticate(password, function(authError, authenticated) {
+ if (authError) return done(authError);
+ if (!authenticated) {
+ return done(null, false, { message: 'This password is not correct.' });
+ } else {
+ return done(null, user);
+ }
+ });
});
}
));
-};
\ No newline at end of file
+};
diff --git a/app/templates/server/config/express.js b/app/templates/server/config/express.js
index 2243a7779..2403780f1 100644
--- a/app/templates/server/config/express.js
+++ b/app/templates/server/config/express.js
@@ -17,7 +17,7 @@ var config = require('./environment');<% if (filters.auth) { %>
var passport = require('passport');<% } %><% if (filters.twitterAuth) { %>
var session = require('express-session');
var mongoStore = require('connect-mongo')(session);
-var mongoose = require('mongoose');<% } %>
+var mongoose = require('mongoose-bird')();<% } %>
module.exports = function(app) {
var env = app.get('env');
@@ -57,4 +57,4 @@ module.exports = function(app) {
app.use(morgan('dev'));
app.use(errorHandler()); // Error handler - has to be last
}
-};
\ No newline at end of file
+};
diff --git a/app/templates/server/config/seed(mongoose).js b/app/templates/server/config/seed(mongoose).js
index 27ab19417..6d56010b6 100644
--- a/app/templates/server/config/seed(mongoose).js
+++ b/app/templates/server/config/seed(mongoose).js
@@ -6,44 +6,51 @@
'use strict';
var Thing = require('../api/thing/thing.model');
-<% if (filters.auth) { %>var User = require('../api/user/user.model');<% } %>
+<% if (filters.auth) { %>
+var User = require('../api/user/user.model');
+<% } %>
-Thing.find({}).remove(function() {
- Thing.create({
- name : 'Development Tools',
- info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.'
- }, {
- name : 'Server and Client integration',
- info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.'
- }, {
- name : 'Smart Build System',
- info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html'
- }, {
- name : 'Modular Structure',
- info : 'Best practice client and server structures allow for more code reusability and maximum scalability'
- }, {
- name : 'Optimized Build',
- info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.'
- },{
- name : 'Deployment Ready',
- info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators'
+Thing.find({}).removeAsync()
+ .then(function() {
+ Thing.create({
+ name : 'Development Tools',
+ info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.'
+ }, {
+ name : 'Server and Client integration',
+ info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.'
+ }, {
+ name : 'Smart Build System',
+ info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html'
+ }, {
+ name : 'Modular Structure',
+ info : 'Best practice client and server structures allow for more code reusability and maximum scalability'
+ }, {
+ name : 'Optimized Build',
+ info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.'
+ },{
+ name : 'Deployment Ready',
+ info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators'
+ });
});
-});<% if (filters.auth) { %>
-User.find({}).remove(function() {
- User.create({
- provider: 'local',
- name: 'Test User',
- email: 'test@test.com',
- password: 'test'
- }, {
- provider: 'local',
- role: 'admin',
- name: 'Admin',
- email: 'admin@admin.com',
- password: 'admin'
- }, function() {
- console.log('finished populating users');
- }
- );
-});<% } %>
\ No newline at end of file
+<% if (filters.auth) { %>
+
+User.find({}).removeAsync()
+ .then(function() {
+ User.create({
+ provider: 'local',
+ name: 'Test User',
+ email: 'test@test.com',
+ password: 'test'
+ }, {
+ provider: 'local',
+ role: 'admin',
+ name: 'Admin',
+ email: 'admin@admin.com',
+ password: 'admin'
+ }, function() {
+ console.log('finished populating users');
+ }
+ );
+ });
+<% } %>
diff --git a/endpoint/templates/index.spec.js b/endpoint/templates/index.spec.js
new file mode 100644
index 000000000..648a841a0
--- /dev/null
+++ b/endpoint/templates/index.spec.js
@@ -0,0 +1,85 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+ /* <%= name %>.controller stub */
+var <%= cameledName %>Ctrl = {
+ index: '<%= name %>Ctrl.index'<% if(filters.mongoose) { %>,
+ show: '<%= name %>Ctrl.show',
+ create: '<%= name %>Ctrl.create',
+ update: '<%= name %>Ctrl.update',
+ destroy: '<%= name %>Ctrl.destroy'<% } %>
+ },
+ /* express.Router().router stub */
+ router = {
+ get: sinon.spy()<% if(filters.mongoose) { %>,
+ put: sinon.spy(),
+ patch: sinon.spy(),
+ post: sinon.spy(),
+ delete: sinon.spy()<% } %>
+ },
+ /* stubbed <%= name %> router */
+ index = proxyquire('./index.js', {
+ 'express': {
+ Router: function() {
+ return router;
+ }
+ },
+ './<%= name %>.controller': <%= cameledName %>Ctrl
+ });
+
+describe('<%= classedName %> API Router:', function() {
+
+ it('should return an express router instance', function() {
+ index.should.equal(router);
+ });
+
+ describe('GET <%= route %>', function() {
+
+ it('should route to <%= name %>.controller.index', function() {
+ router.get.withArgs('/', '<%= name %>Ctrl.index').should.have.been.calledOnce;
+ });
+
+ });<% if(filters.mongoose) { %>
+
+ describe('GET <%= route %>/:id', function() {
+
+ it('should route to <%= name %>.controller.show', function() {
+ router.get.withArgs('/:id', '<%= name %>Ctrl.show').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('POST <%= route %>', function() {
+
+ it('should route to <%= name %>.controller.create', function() {
+ router.post.withArgs('/', '<%= name %>Ctrl.create').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('PUT <%= route %>/:id', function() {
+
+ it('should route to <%= name %>.controller.update', function() {
+ router.put.withArgs('/:id', '<%= name %>Ctrl.update').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('PATCH <%= route %>/:id', function() {
+
+ it('should route to <%= name %>.controller.update', function() {
+ router.patch.withArgs('/:id', '<%= name %>Ctrl.update').should.have.been.calledOnce;
+ });
+
+ });
+
+ describe('DELETE <%= route %>/:id', function() {
+
+ it('should route to <%= name %>.controller.destroy', function() {
+ router.delete.withArgs('/:id', '<%= name %>Ctrl.destroy').should.have.been.calledOnce;
+ });
+
+ });<% } %>
+
+});
diff --git a/endpoint/templates/name.controller.js b/endpoint/templates/name.controller.js
index 1d9da544e..5851a1338 100644
--- a/endpoint/templates/name.controller.js
+++ b/endpoint/templates/name.controller.js
@@ -1,60 +1,94 @@
-'use strict';
+'use strict';<% if (filters.mongoose) { %>
-var _ = require('lodash');<% if (filters.mongoose) { %>
-var <%= classedName %> = require('./<%= name %>.model');<% } %>
+var _ = require('lodash');
+var <%= classedName %> = require('./<%= name %>.model');
+
+function handleError(res, statusCode){
+ statusCode = statusCode || 500;
+ return function(err){
+ res.send(statusCode, err);
+ };
+}
+
+function responseWithResult(res, statusCode){
+ statusCode = statusCode || 200;
+ return function(entity){
+ if(entity){
+ return res.json(statusCode, entity);
+ }
+ };
+}
+
+function handleEntityNotFound(res){
+ return function(entity){
+ if(!entity){
+ res.send(404);
+ return null;
+ }
+ return entity;
+ };
+}
+
+function saveUpdates(updates){
+ return function(entity){
+ var updated = _.merge(entity, updates);
+ return updated.saveAsync()
+ .then(function () {
+ return updated;
+ });
+ };
+}
+
+function removeEntity(res){
+ return function (entity) {
+ if(entity){
+ return entity.removeAsync()
+ .then(function() {
+ return res.send(204);
+ });
+ }
+ };
+}<% } %>
// Get list of <%= name %>s
exports.index = function(req, res) {<% if (!filters.mongoose) { %>
- res.json([]);<% } %><% if (filters.mongoose) { %>
- <%= classedName %>.find(function (err, <%= name %>s) {
- if(err) { return handleError(res, err); }
- return res.json(200, <%= name %>s);
- });<% } %>
+ res.json([]);<% } if (filters.mongoose) { %>
+ <%= classedName %>.findAsync()
+ .then(responseWithResult(res))
+ .catch(handleError(res));<% } %>
};<% if (filters.mongoose) { %>
// Get a single <%= name %>
exports.show = function(req, res) {
- <%= classedName %>.findById(req.params.id, function (err, <%= name %>) {
- if(err) { return handleError(res, err); }
- if(!<%= name %>) { return res.send(404); }
- return res.json(<%= name %>);
- });
+ <%= classedName %>.findByIdAsync(req.params.id)
+ .then(handleEntityNotFound(res))
+ .then(responseWithResult(res))
+ .catch(handleError(res));
};
// Creates a new <%= name %> in the DB.
exports.create = function(req, res) {
- <%= classedName %>.create(req.body, function(err, <%= name %>) {
- if(err) { return handleError(res, err); }
- return res.json(201, <%= name %>);
- });
+ <%= classedName %>.createAsync(req.body)
+ .then(responseWithResult(res, 201))
+ .catch(handleError(res));
};
// Updates an existing <%= name %> in the DB.
exports.update = function(req, res) {
- if(req.body._id) { delete req.body._id; }
- <%= classedName %>.findById(req.params.id, function (err, <%= name %>) {
- if (err) { return handleError(res, err); }
- if(!<%= name %>) { return res.send(404); }
- var updated = _.merge(<%= name %>, req.body);
- updated.save(function (err) {
- if (err) { return handleError(res, err); }
- return res.json(200, <%= name %>);
- });
- });
+ if(req.body._id) {
+ delete req.body._id;
+ }
+ <%= classedName %>.findByIdAsync(req.params.id)
+ .then(handleEntityNotFound(res))
+ .then(saveUpdates(req.body))
+ .then(responseWithResult(res))
+ .catch(handleError(res));
};
// Deletes a <%= name %> from the DB.
exports.destroy = function(req, res) {
- <%= classedName %>.findById(req.params.id, function (err, <%= name %>) {
- if(err) { return handleError(res, err); }
- if(!<%= name %>) { return res.send(404); }
- <%= name %>.remove(function(err) {
- if(err) { return handleError(res, err); }
- return res.send(204);
- });
- });
-};
-
-function handleError(res, err) {
- return res.send(500, err);
-}<% } %>
\ No newline at end of file
+ <%= classedName %>.findByIdAsync(req.params.id)
+ .then(handleEntityNotFound(res))
+ .then(removeEntity(res))
+ .catch(handleError(res));
+};<% } %>
diff --git a/endpoint/templates/name.e2e.js b/endpoint/templates/name.e2e.js
new file mode 100644
index 000000000..f168f1ca9
--- /dev/null
+++ b/endpoint/templates/name.e2e.js
@@ -0,0 +1,135 @@
+'use strict';
+
+var app = require('../../app');
+var request = require('supertest');<% if(filters.mongoose) { %>
+
+var new<%= classedName %>;<% } %>
+
+describe('<%= classedName %> API:', function() {
+
+ describe('GET <%= route %>', function() {
+ var <%= cameledName %>s;
+
+ beforeEach(function(done) {
+ request(app)
+ .get('<%= route %>')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ <%= cameledName %>s = res.body;
+ done();
+ });
+ });
+
+ it('should respond with JSON array', function() {
+ <%= cameledName %>s.should.be.instanceOf(Array);
+ });
+
+ });<% if(filters.mongoose) { %>
+
+ describe('POST <%= route %>', function() {
+ beforeEach(function(done) {
+ request(app)
+ .post('<%= route %>')
+ .send({
+ name: 'New <%= classedName %>',
+ info: 'This is the brand new <%= name %>!!!'
+ })
+ .expect(201)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ new<%= classedName %> = res.body;
+ done();
+ });
+ });
+
+ it('should respond with the newly created <%= name %>', function() {
+ new<%= classedName %>.name.should.equal('New <%= classedName %>');
+ new<%= classedName %>.info.should.equal('This is the brand new <%= name %>!!!');
+ });
+
+ });
+
+ describe('GET <%= route %>/:id', function() {
+ var <%= cameledName %>;
+
+ beforeEach(function(done) {
+ request(app)
+ .get('<%= route %>/' + new<%= classedName %>._id)
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ <%= cameledName %> = res.body;
+ done();
+ });
+ });
+
+ afterEach(function() {
+ <%= cameledName %> = {};
+ });
+
+ it('should respond with the requested <%= name %>', function() {
+ <%= cameledName %>.name.should.equal('New <%= classedName %>');
+ <%= cameledName %>.info.should.equal('This is the brand new <%= name %>!!!');
+ });
+
+ });
+
+ describe('PUT <%= route %>/:id', function() {
+ var updated<%= classedName %>
+
+ beforeEach(function(done) {
+ request(app)
+ .put('<%= route %>/' + new<%= classedName %>._id)
+ .send({
+ name: 'Updated <%= classedName %>',
+ info: 'This is the updated <%= name %>!!!'
+ })
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ updated<%= classedName %> = res.body;
+ done();
+ });
+ });
+
+ afterEach(function() {
+ updated<%= classedName %> = {};
+ });
+
+ it('should respond with the updated <%= name %>', function() {
+ updated<%= classedName %>.name.should.equal('Updated <%= classedName %>');
+ updated<%= classedName %>.info.should.equal('This is the updated <%= name %>!!!');
+ });
+
+ });
+
+ describe('DELETE <%= route %>/:id', function() {
+
+ it('should respond with 204 on successful removal', function(done) {
+ request(app)
+ .delete('<%= route %>/' + new<%= classedName %>._id)
+ .expect(204)
+ .end(function(err, res) {
+ if (err) return done(err);
+ done();
+ });
+ });
+
+ it('should respond with 404 when <%= name %> does not exsist', function(done) {
+ request(app)
+ .delete('<%= route %>/' + new<%= classedName %>._id)
+ .expect(404)
+ .end(function(err, res) {
+ if (err) return done(err);
+ done();
+ });
+ });
+
+ });<% } %>
+
+});
diff --git a/endpoint/templates/name.model(mongoose).js b/endpoint/templates/name.model(mongoose).js
index 89e0dfaa7..9b23d9d41 100644
--- a/endpoint/templates/name.model(mongoose).js
+++ b/endpoint/templates/name.model(mongoose).js
@@ -1,6 +1,6 @@
'use strict';
-var mongoose = require('mongoose'),
+var mongoose = require('mongoose-bird')(),
Schema = mongoose.Schema;
var <%= classedName %>Schema = new Schema({
@@ -9,4 +9,4 @@ var <%= classedName %>Schema = new Schema({
active: Boolean
});
-module.exports = mongoose.model('<%= classedName %>', <%= classedName %>Schema);
\ No newline at end of file
+module.exports = mongoose.model('<%= classedName %>', <%= classedName %>Schema);
diff --git a/endpoint/templates/name.spec.js b/endpoint/templates/name.spec.js
deleted file mode 100644
index fcad73ebd..000000000
--- a/endpoint/templates/name.spec.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-var should = require('should');
-var app = require('../../app');
-var request = require('supertest');
-
-describe('GET <%= route %>', function() {
-
- it('should respond with JSON array', function(done) {
- request(app)
- .get('<%= route %>')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(function(err, res) {
- if (err) return done(err);
- res.body.should.be.instanceof(Array);
- done();
- });
- });
-});
\ No newline at end of file
diff --git a/package.json b/package.json
index 09e17ff62..ec61432c7 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"marked": "~0.2.8",
"mocha": "~1.21.0",
"q": "^1.0.1",
+ "recursive-readdir": "^1.2.0",
"semver": "~2.2.1",
"shelljs": "^0.3.0",
"underscore.string": "^2.3.3"
diff --git a/test/fixtures/package.json b/test/fixtures/package.json
index ce4df22f9..48d48607e 100644
--- a/test/fixtures/package.json
+++ b/test/fixtures/package.json
@@ -16,6 +16,7 @@
"jade": "~1.2.0",
"ejs": "~0.8.4",
"mongoose": "~3.8.8",
+ "mongoose-bird": "~0.0.1",
"jsonwebtoken": "^0.3.0",
"express-jwt": "^0.1.3",
"passport": "~0.2.0",
@@ -30,6 +31,7 @@
"socketio-jwt": "^2.0.2"
},
"devDependencies": {
+ "chai-as-promised": "^4.1.1",
"grunt": "~0.4.4",
"grunt-autoprefixer": "~0.7.2",
"grunt-wiredep": "~1.8.0",
@@ -45,7 +47,7 @@
"grunt-contrib-watch": "~0.6.1",
"grunt-contrib-coffee": "^0.10.1",
"grunt-contrib-jade": "^0.11.0",
- "grunt-contrib-less": "^0.11.0",
+ "grunt-contrib-less": "^0.11.4",
"grunt-google-cdn": "~0.4.0",
"grunt-newer": "~0.7.0",
"grunt-ng-annotate": "^0.2.3",
@@ -62,6 +64,7 @@
"grunt-karma": "~0.8.2",
"grunt-build-control": "DaftMonk/grunt-build-control",
"grunt-mocha-test": "~0.10.2",
+ "grunt-mocha-istanbul": "^2.0.0",
"grunt-contrib-sass": "^0.7.3",
"grunt-contrib-stylus": "latest",
"jit-grunt": "^0.5.0",
@@ -85,8 +88,9 @@
"karma-phantomjs-launcher": "~0.1.4",
"karma": "~0.12.9",
"karma-ng-html2js-preprocessor": "~0.1.0",
+ "proxyquire": "^1.0.1",
"supertest": "~0.11.0",
- "should": "~3.3.1"
+ "sinon-chai": "^2.5.0"
},
"engines": {
"node": ">=0.10.0"
diff --git a/test/test-file-creation.js b/test/test-file-creation.js
index 774a6f658..05e83c77f 100644
--- a/test/test-file-creation.js
+++ b/test/test-file-creation.js
@@ -1,11 +1,12 @@
/*global describe, beforeEach, it */
'use strict';
var path = require('path');
+var fs = require('fs-extra');
+var exec = require('child_process').exec;
var helpers = require('yeoman-generator').test;
var chai = require('chai');
var expect = chai.expect;
-var fs = require('fs-extra');
-var exec = require('child_process').exec;
+var recursiveReadDir = require('recursive-readdir');
describe('angular-fullstack generator', function () {
var gen, defaultOptions = {
@@ -34,6 +35,219 @@ describe('angular-fullstack generator', function () {
});
}
+ function assertOnlyFiles(expectedFiles, done, path, skip) {
+ path = path || './';
+ skip = skip || ['e2e', 'node_modules', 'client/bower_components'];
+
+ recursiveReadDir(path, skip, function(err, actualFiles) {
+ if (err) { return done(err); }
+ var files = actualFiles.concat();
+
+ expectedFiles.forEach(function(file, i) {
+ var index = files.indexOf(file);
+ if (index >= 0) {
+ files.splice(index, 1);
+ }
+ });
+
+ if (files.length !== 0) {
+ err = new Error('unexpected files found');
+ err.expected = '';
+ err.actual = files.join('\n');
+ return done(err);
+ }
+
+ done();
+ });
+ }
+
+ function runTest(cmd, self, cb) {
+ var args = Array.prototype.slice.call(arguments),
+ endpoint = (args[3] && typeof args[3] === 'string') ? args.splice(3, 1)[0] : null,
+ timeout = (args[3] && typeof args[3] === 'number') ? args.splice(3, 1)[0] : null;
+
+ self.timeout(timeout || 60000);
+
+ var execFn = function() {
+ exec(cmd, function(error, stdout, stderr) {
+ switch(cmd) {
+ case 'grunt test:client':
+ expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1\u001b[32m SUCCESS\u001b');
+ break;
+ case 'grunt jshint':
+ expect(stdout).to.contain('Done, without errors.');
+ break;
+ case 'grunt test:server':
+ expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
+ break;
+ default:
+ expect(stderr).to.be.empty;
+ }
+
+ cb();
+ });
+ };
+
+ if (endpoint) {
+ generatorTest('endpoint', endpoint, {}, execFn);
+ } else {
+ gen.run({}, execFn);
+ }
+ }
+
+ function genFiles(ops) {
+ var mapping = {
+ stylesheet: {
+ sass: 'scss',
+ stylus: 'styl',
+ less: 'less',
+ css: 'css'
+ },
+ markup: {
+ jade: 'jade',
+ html: 'html'
+ },
+ script: {
+ js: 'js',
+ coffee: 'coffee'
+ }
+ },
+ files = [];
+
+ var oauthFiles = function(type) {
+ return [
+ 'server/auth/' + type + '/index.js',
+ 'server/auth/' + type + '/passport.js',
+ ];
+ };
+
+
+ var script = mapping.script[ops.script],
+ markup = mapping.markup[ops.markup],
+ stylesheet = mapping.stylesheet[ops.stylesheet];
+
+ files = files.concat([
+ 'client/.htaccess',
+ 'client/.jshintrc',
+ 'client/favicon.ico',
+ 'client/robots.txt',
+ 'client/index.html',
+ 'client/app/app.' + script,
+ 'client/app/app.' + stylesheet,
+ 'client/app/main/main.' + script,
+ 'client/app/main/main.' + markup,
+ 'client/app/main/main.' + stylesheet,
+ 'client/app/main/main.controller.' + script,
+ 'client/app/main/main.controller.spec.' + script,
+ 'client/assets/images/yeoman.png',
+ 'client/components/navbar/navbar.' + markup,
+ 'client/components/navbar/navbar.controller.' + script,
+ 'server/.jshintrc',
+ 'server/.jshintrc-spec',
+ 'server/app.js',
+ 'server/routes.js',
+ 'server/api/thing/index.js',
+ 'server/api/thing/index.spec.js',
+ 'server/api/thing/thing.controller.js',
+ 'server/api/thing/thing.e2e.js',
+ 'server/components/errors/index.js',
+ 'server/config/local.env.js',
+ 'server/config/local.env.sample.js',
+ 'server/config/express.js',
+ 'server/config/environment/index.js',
+ 'server/config/environment/development.js',
+ 'server/config/environment/production.js',
+ 'server/config/environment/test.js',
+ 'server/views/404.' + markup,
+ '.bowerrc',
+ '.buildignore',
+ '.editorconfig',
+ '.gitattributes',
+ '.gitignore',
+ '.travis.yml',
+ '.yo-rc.json',
+ 'Gruntfile.js',
+ 'package.json',
+ 'bower.json',
+ 'karma.conf.js',
+ 'mocha.conf.js',
+ 'protractor.conf.js'
+ ]);
+
+ if (ops.router === 'uirouter') {
+ files = files.concat([
+ 'client/components/ui-router/ui-router.mock.' + script
+ ]);
+ }
+
+ if (ops.uibootstrap) {
+ files = files.concat([
+ 'client/components/modal/modal.' + markup,
+ 'client/components/modal/modal.' + stylesheet,
+ 'client/components/modal/modal.service.' + script
+ ]);
+ }
+
+ if (ops.mongoose) {
+ files = files.concat([
+ 'server/api/thing/thing.model.js',
+ 'server/config/seed.js'
+ ]);
+ }
+
+ if (ops.auth) {
+ files = files.concat([
+ 'client/app/account/account.' + script,
+ 'client/app/account/login/login.' + markup,
+ 'client/app/account/login/login.' + stylesheet,
+ 'client/app/account/login/login.controller.' + script,
+ 'client/app/account/settings/settings.' + markup,
+ 'client/app/account/settings/settings.controller.' + script,
+ 'client/app/account/signup/signup.' + markup,
+ 'client/app/account/signup/signup.controller.' + script,
+ 'client/app/admin/admin.' + markup,
+ 'client/app/admin/admin.' + stylesheet,
+ 'client/app/admin/admin.' + script,
+ 'client/app/admin/admin.controller.' + script,
+ 'client/components/auth/auth.service.' + script,
+ 'client/components/auth/user.service.' + script,
+ 'client/components/mongoose-error/mongoose-error.directive.' + script,
+ 'server/api/user/index.js',
+ 'server/api/user/index.spec.js',
+ 'server/api/user/user.controller.js',
+ 'server/api/user/user.e2e.js',
+ 'server/api/user/user.model.js',
+ 'server/api/user/user.model.spec.js',
+ 'server/auth/index.js',
+ 'server/auth/auth.service.js',
+ 'server/auth/local/index.js',
+ 'server/auth/local/passport.js'
+ ]);
+ }
+
+ if (ops.oauth) {
+ ops.oauth.forEach(function(type, i) {
+ files = files.concat(oauthFiles(type.replace('Auth', '')));
+ });
+ }
+
+ if (ops.socketio) {
+ files = files.concat([
+ 'client/components/socket/socket.service.' + script,
+ 'client/components/socket/socket.mock.' + script,
+ 'server/api/thing/thing.socket.js',
+ 'server/config/socketio.js'
+ ]);
+ }
+
+ return files;
+ }
+
+
+ /**
+ * Generator tests
+ */
+
beforeEach(function (done) {
this.timeout(10000);
var deps = [
@@ -70,43 +284,31 @@ describe('angular-fullstack generator', function () {
});
it('should run client tests successfully', function(done) {
- this.timeout(60000);
- gen.run({}, function () {
- exec('grunt test:client', function (error, stdout, stderr) {
- expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1\u001b[32m SUCCESS\u001b');
- done();
- });
- });
+ runTest('grunt test:client', this, done);
});
- it('should pass jshint', function(done) {
- this.timeout(60000);
- gen.run({}, function () {
- exec('grunt jshint', function (error, stdout, stderr) {
- expect(stdout).to.contain('Done, without errors.');
- done();
- });
- });
+ it('should pass lint', function(done) {
+ runTest('grunt jshint', this, done);
});
it('should run server tests successfully', function(done) {
- this.timeout(60000);
- gen.run({}, function () {
- exec('grunt test:server', function (error, stdout, stderr) {
- expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
- done();
- });
- });
+ runTest('grunt test:server', this, done);
+ });
+
+ it('should pass lint with generated endpoint', function(done) {
+ runTest('grunt jshint', this, done, 'foo');
});
it('should run server tests successfully with generated endpoint', function(done) {
- this.timeout(60000);
- generatorTest('endpoint', 'foo', {}, function() {
- exec('grunt test:server', function (error, stdout, stderr) {
- expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
- done();
- });
- });
+ runTest('grunt test:server', this, done, 'foo');
+ });
+
+ it('should pass lint with generated capitalized endpoint', function(done) {
+ runTest('grunt jshint', this, done, 'Foo');
+ });
+
+ it('should run server tests successfully with generated capitalized endpoint', function(done) {
+ runTest('grunt test:server', this, done, 'Foo');
});
it('should use existing config if available', function(done) {
@@ -132,6 +334,19 @@ describe('angular-fullstack generator', function () {
});
});
+ it('should generate expected files', function (done) {
+ gen.run({}, function () {
+ helpers.assertFile(genFiles(defaultOptions));
+ done();
+ });
+ });
+
+ it('should not generate unexpected files', function (done) {
+ gen.run({}, function () {
+ assertOnlyFiles(genFiles(defaultOptions), done);
+ });
+ });
+
// it('should run e2e tests successfully', function(done) {
// this.timeout(80000);
// gen.run({}, function () {
@@ -146,166 +361,153 @@ describe('angular-fullstack generator', function () {
});
describe('with other preprocessors and oauth', function() {
+ var testOptions = {
+ script: 'coffee',
+ markup: 'jade',
+ stylesheet: 'less',
+ router: 'uirouter',
+ mongoose: true,
+ auth: true,
+ oauth: ['twitterAuth', 'facebookAuth', 'googleAuth'],
+ socketio: true,
+ bootstrap: true,
+ uibootstrap: true
+ };
+
beforeEach(function() {
- helpers.mockPrompt(gen, {
- script: 'coffee',
- markup: 'jade',
- stylesheet: 'less',
- router: 'uirouter',
- mongoose: true,
- auth: true,
- oauth: ['twitterAuth', 'facebookAuth', 'googleAuth'],
- socketio: true
- });
+ helpers.mockPrompt(gen, testOptions);
});
it('should run client tests successfully', function(done) {
- this.timeout(60000);
- gen.run({}, function () {
- exec('grunt test:client', function (error, stdout, stderr) {
- expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1\u001b[32m SUCCESS\u001b');
- done();
- });
- });
+ runTest('grunt test:client', this, done);
});
- it('should pass jshint', function(done) {
- this.timeout(60000);
+ it('should pass lint', function(done) {
+ runTest('grunt jshint', this, done);
+ });
+
+ it('should run server tests successfully', function(done) {
+ runTest('grunt test:server', this, done);
+ });
+
+ it('should pass lint with generated snake-case endpoint', function(done) {
+ runTest('grunt jshint', this, done, 'foo-bar');
+ });
+
+ it('should run server tests successfully with generated snake-case endpoint', function(done) {
+ runTest('grunt test:server', this, done, 'foo-bar');
+ });
+
+ it('should generate expected files', function (done) {
gen.run({}, function () {
- exec('grunt jshint', function (error, stdout, stderr) {
- expect(stdout).to.contain('Done, without errors.');
- done();
- });
+ helpers.assertFile(genFiles(testOptions));
+ done();
});
});
- it('should run server tests successfully', function(done) {
- this.timeout(60000);
+ it('should not generate unexpected files', function (done) {
gen.run({}, function () {
- exec('grunt test:server', function (error, stdout, stderr) {
- expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
- done();
- });
+ assertOnlyFiles(genFiles(testOptions), done);
});
});
});
describe('with other preprocessors and no server options', function() {
+ var testOptions = {
+ script: 'coffee',
+ markup: 'jade',
+ stylesheet: 'stylus',
+ router: 'ngroute',
+ mongoose: false,
+ auth: false,
+ oauth: [],
+ socketio: false,
+ bootstrap: false,
+ uibootstrap: false
+ };
+
beforeEach(function(done) {
- helpers.mockPrompt(gen, {
- script: 'coffee',
- markup: 'jade',
- stylesheet: 'stylus',
- router: 'ngroute',
- mongoose: false,
- auth: false,
- oauth: [],
- socketio: false
- });
+ helpers.mockPrompt(gen, testOptions);
done();
});
it('should run client tests successfully', function(done) {
- this.timeout(60000);
- gen.run({}, function () {
- exec('grunt test:client', function (error, stdout, stderr) {
- expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1\u001b[32m SUCCESS\u001b');
- done();
- });
- });
+ runTest('grunt test:client', this, done);
});
- it('should pass jshint', function(done) {
- this.timeout(60000);
+ it('should pass lint', function(done) {
+ runTest('grunt jshint', this, done);
+ });
+
+ it('should run server tests successfully', function(done) {
+ runTest('grunt test:server', this, done);
+ });
+
+ it('should pass lint with generated endpoint', function(done) {
+ runTest('grunt jshint', this, done, 'foo');
+ });
+
+ it('should run server tests successfully with generated endpoint', function(done) {
+ runTest('grunt test:server', this, done, 'foo');
+ });
+
+ it('should generate expected files', function (done) {
gen.run({}, function () {
- exec('grunt jshint', function (error, stdout, stderr) {
- expect(stdout).to.contain('Done, without errors.');
- done();
- });
+ helpers.assertFile(genFiles(testOptions));
+ done();
});
});
- it('should run server tests successfully', function(done) {
- this.timeout(60000);
+ it('should not generate unexpected files', function (done) {
gen.run({}, function () {
- exec('grunt test:server', function (error, stdout, stderr) {
- expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
- done();
- });
+ assertOnlyFiles(genFiles(testOptions), done);
});
});
});
describe('with no preprocessors and no server options', function() {
+ var testOptions = {
+ script: 'js',
+ markup: 'html',
+ stylesheet: 'css',
+ router: 'ngroute',
+ mongoose: false,
+ auth: false,
+ oauth: [],
+ socketio: false,
+ bootstrap: true,
+ uibootstrap: true
+ };
+
beforeEach(function(done) {
- helpers.mockPrompt(gen, {
- script: 'js',
- markup: 'html',
- stylesheet: 'css',
- router: 'ngroute',
- mongoose: false,
- auth: false,
- oauth: [],
- socketio: false
- });
+ helpers.mockPrompt(gen, testOptions);
done();
});
it('should run client tests successfully', function(done) {
- this.timeout(60000);
- gen.run({}, function () {
- exec('grunt test:client', function (error, stdout, stderr) {
- expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1\u001b[32m SUCCESS\u001b');
- done();
- });
- });
+ runTest('grunt test:client', this, done);
});
- it('should pass jshint', function(done) {
- this.timeout(60000);
- gen.run({}, function () {
- exec('grunt jshint', function (error, stdout, stderr) {
- expect(stdout).to.contain('Done, without errors.');
- done();
- });
- });
+ it('should pass lint', function(done) {
+ runTest('grunt jshint', this, done);
});
it('should run server tests successfully', function(done) {
- this.timeout(60000);
- gen.run({}, function () {
- exec('grunt test:server', function (error, stdout, stderr) {
- expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
- done();
- });
- });
+ runTest('grunt test:server', this, done);
});
it('should generate expected files', function (done) {
- helpers.mockPrompt(gen, defaultOptions);
-
gen.run({}, function () {
- helpers.assertFile([
- 'client/.htaccess',
- 'client/favicon.ico',
- 'client/robots.txt',
- 'client/app/main/main.scss',
- 'client/app/main/main.html',
- 'client/index.html',
- 'client/.jshintrc',
- 'client/assets/images/yeoman.png',
- '.bowerrc',
- '.editorconfig',
- '.gitignore',
- 'Gruntfile.js',
- 'package.json',
- 'bower.json',
- 'server/app.js',
- 'server/config/express.js',
- 'server/api/thing/index.js']);
+ helpers.assertFile(genFiles(testOptions));
done();
});
});
+
+ it('should not generate unexpected files', function (done) {
+ gen.run({}, function () {
+ assertOnlyFiles(genFiles(testOptions), done);
+ });
+ });
});
});
});