From 476eaa8b845e7bc6369c1f6d7cd5ac19b643ea5e Mon Sep 17 00:00:00 2001 From: Henrique Santos Date: Thu, 21 Jan 2016 18:42:50 -0200 Subject: [PATCH 1/7] add notification backend and unit test add notification init frontend --- .../NotificationsController.coffee | 15 ++++++ .../Notifications/NotificationsHandler.coffee | 29 +++++++++++ .../Features/Project/ProjectController.coffee | 21 ++++---- services/web/app/coffee/router.coffee | 24 +++++----- services/web/app/views/project/list.jade | 4 +- .../app/views/project/list/notifications.jade | 18 +++++++ services/web/config/settings.defaults.coffee | 4 +- .../coffee/main/project-list/index.coffee | 1 + .../notifications-controller.coffee | 5 ++ .../main/project-list/project-list.coffee | 1 + .../NotificationsControllerTests.coffee | 39 +++++++++++++++ .../NotificationsHandlerTests.coffee | 48 +++++++++++++++++++ 12 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 services/web/app/coffee/Features/Notifications/NotificationsController.coffee create mode 100644 services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee create mode 100644 services/web/app/views/project/list/notifications.jade create mode 100644 services/web/public/coffee/main/project-list/notifications-controller.coffee create mode 100644 services/web/test/UnitTests/coffee/Notifications/NotificationsControllerTests.coffee create mode 100644 services/web/test/UnitTests/coffee/Notifications/NotificationsHandlerTests.coffee diff --git a/services/web/app/coffee/Features/Notifications/NotificationsController.coffee b/services/web/app/coffee/Features/Notifications/NotificationsController.coffee new file mode 100644 index 0000000000..7137dfa986 --- /dev/null +++ b/services/web/app/coffee/Features/Notifications/NotificationsController.coffee @@ -0,0 +1,15 @@ +NotificationsHandler = require("./NotificationsHandler") +logger = require("logger-sharelatex") + +module.exports = + + getAllUnreadNotifications: (req, res)-> + NotificationsHandler.getUserNotifications req.session.user._id, (err, unreadNotifications)-> + res.send(unreadNotifications) + + markNotificationAsRead: (req, res)-> + user_id = req.session.user._id + notification_id = req.params.notification_id + NotificationsHandler.markAsRead user_id, notification_id, -> + res.send() + logger.log user_id:user_id, notification_id:notification_id, "mark notification as read" diff --git a/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee new file mode 100644 index 0000000000..1cf11af1f9 --- /dev/null +++ b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee @@ -0,0 +1,29 @@ +settings = require("settings-sharelatex") +request = require("request") +logger = require("logger-sharelatex") + +oneSecond = 1000 +module.exports = + + getUserNotifications: (user_id, callback)-> + opts = + uri: "#{settings.apis.notifications.url}/user/#{user_id}" + json: true + timeout: 2000 + request.get opts, (err, res, unreadNotifications)-> + statusCode = if res? then res.statusCode else 500 + if err? or statusCode != 200 + e = new Error("something went wrong getting notifications, #{err}, #{statusCode}") + logger.err err:err + callback(e, []) + else + if !unreadNotifications? + unreadNotifications = [] + callback(null, unreadNotifications) + + markAsRead: (user_id, notification_id, callback)-> + opts = + uri: "#{settings.apis.notifications.url}/user/#{user_id}/notification/#{notification_id}" + timeout:oneSecond + logger.log user_id:user_id, notification_id:notification_id, "send mark notification to notifications api" + request.del opts, callback diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index 1d0a70642a..b3a5635f97 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -8,6 +8,7 @@ metrics = require('../../infrastructure/Metrics') Project = require('../../models/Project').Project User = require('../../models/User').User TagsHandler = require("../Tags/TagsHandler") +NotificationsHandler = require("../Notifications/NotificationsHandler") SubscriptionLocator = require("../Subscription/SubscriptionLocator") LimitationsManager = require("../Subscription/LimitationsManager") _ = require("underscore") @@ -16,8 +17,6 @@ SecurityManager = require("../../managers/SecurityManager") fs = require "fs" InactiveProjectManager = require("../InactiveData/InactiveProjectManager") ProjectUpdateHandler = require("./ProjectUpdateHandler") -ReferencesSearchHandler = require("../ReferencesSearch/ReferencesSearchHandler") - module.exports = ProjectController = @@ -127,6 +126,8 @@ module.exports = ProjectController = async.parallel { tags: (cb)-> TagsHandler.getAllTags user_id, cb + notifications: (cb)-> + NotificationsHandler.getUserNotifications user_id, cb projects: (cb)-> Project.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref', cb hasSubscription: (cb)-> @@ -139,6 +140,7 @@ module.exports = ProjectController = return next(err) logger.log results:results, user_id:user_id, "rendering project list" tags = results.tags[0] + notifications = results.notifications projects = ProjectController._buildProjectList results.projects[0], results.projects[1], results.projects[2] user = results.user ProjectController._injectProjectOwners projects, (error, projects) -> @@ -149,6 +151,7 @@ module.exports = ProjectController = priority_title: true projects: projects tags: tags + notifications: notifications user: user hasSubscription: results.hasSubscription[0] } @@ -170,15 +173,15 @@ module.exports = ProjectController = return res.render("general/closed", {title:"updating_site"}) if req.session.user? - user_id = req.session.user._id + user_id = req.session.user._id anonymous = false else anonymous = true user_id = 'openUser' - + project_id = req.params.Project_id logger.log project_id:project_id, "loading editor" - + async.parallel { project: (cb)-> Project.findPopulatedById project_id, cb @@ -195,7 +198,7 @@ module.exports = ProjectController = SubscriptionLocator.getUsersSubscription user_id, cb activate: (cb)-> InactiveProjectManager.reactivateProjectIfRequired project_id, cb - markAsOpened: (cb)-> + markAsOpened: (cb)-> #don't need to wait for this to complete ProjectUpdateHandler.markAsOpened project_id, -> cb() @@ -207,7 +210,6 @@ module.exports = ProjectController = user = results.user subscription = results.subscription - daysSinceLastUpdated = (new Date() - project.lastUpdated) /86400000 logger.log project_id:project_id, daysSinceLastUpdated:daysSinceLastUpdated, "got db results for loading editor" @@ -217,10 +219,6 @@ module.exports = ProjectController = if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt? allowedFreeTrial = !!subscription.freeTrial.allowed || true - - # HACK: don't do it for now - ReferencesSearchHandler.indexProjectReferences project, -> # don't need to wait on this - logger.log project_id:project_id, "rendering editor page" res.render 'project/editor', title: project.name @@ -318,3 +316,4 @@ do generateThemeList = () -> if file.slice(-2) == "js" and file.match(/^theme-/) cleanName = file.slice(0,-3).slice(6) THEME_LIST.push cleanName + diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index c3eec34c6a..f40d629510 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -16,6 +16,7 @@ ReferalController = require('./Features/Referal/ReferalController') ReferalMiddleware = require('./Features/Referal/ReferalMiddleware') AuthenticationController = require('./Features/Authentication/AuthenticationController') TagsController = require("./Features/Tags/TagsController") +NotificationsController = require("./Features/Notifications/NotificationsController") CollaboratorsRouter = require('./Features/Collaborators/CollaboratorsRouter') UserInfoController = require('./Features/User/UserInfoController') UserController = require("./Features/User/UserController") @@ -37,7 +38,6 @@ RateLimiterMiddlewear = require('./Features/Security/RateLimiterMiddlewear') RealTimeProxyRouter = require('./Features/RealTimeProxy/RealTimeProxyRouter') InactiveProjectController = require("./Features/InactiveData/InactiveProjectController") ContactRouter = require("./Features/Contacts/ContactRouter") -ReferencesSearchController = require('./Features/ReferencesSearch/ReferencesSearchController') logger = require("logger-sharelatex") _ = require("underscore") @@ -47,7 +47,7 @@ module.exports = class Router if !Settings.allowPublicAccess webRouter.all '*', AuthenticationController.requireGlobalLogin - + webRouter.get '/login', UserPagesController.loginPage AuthenticationController.addEndpointToLoginWhitelist '/login' @@ -68,18 +68,18 @@ module.exports = class Router StaticPagesRouter.apply(webRouter, apiRouter) RealTimeProxyRouter.apply(webRouter, apiRouter) ContactRouter.apply(webRouter, apiRouter) - + Modules.applyRouter(webRouter, apiRouter) if Settings.enableSubscriptions webRouter.get '/user/bonus', AuthenticationController.requireLogin(), ReferalMiddleware.getUserReferalId, ReferalController.bonus - + webRouter.get '/blog', BlogController.getIndexPage webRouter.get '/blog/*', BlogController.getPage - + webRouter.get '/user/activate', UserPagesController.activateAccountPage - + webRouter.get '/user/settings', AuthenticationController.requireLogin(), UserPagesController.settingsPage webRouter.post '/user/settings', AuthenticationController.requireLogin(), UserController.updateUserSettings webRouter.post '/user/password/update', AuthenticationController.requireLogin(), UserController.changePassword @@ -134,6 +134,9 @@ module.exports = class Router webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags webRouter.post '/project/:project_id/tag', AuthenticationController.requireLogin(), TagsController.processTagsUpdate + webRouter.get '/notifications', AuthenticationController.requireLogin(), NotificationsController.getAllUnreadNotifications + webRouter.delete '/notifications/:notification_id', AuthenticationController.requireLogin(), NotificationsController.markNotificationAsRead + # Deprecated in favour of /internal/project/:project_id but still used by versioning apiRouter.get '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails @@ -159,7 +162,7 @@ module.exports = class Router apiRouter.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate apiRouter.delete '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate - + apiRouter.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents apiRouter.delete '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents @@ -168,12 +171,9 @@ module.exports = class Router webRouter.get "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.getMessages webRouter.post "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.sendMessage - + webRouter.get /learn(\/.*)?/, WikiController.getPage - webRouter.post "/project/:Project_id/references", SecurityManager.requestCanAccessProject, ReferencesSearchController.indexFile - webRouter.get "/project/:Project_id/references/keys", SecurityManager.requestCanAccessProject, ReferencesSearchController.getKeys - #Admin Stuff webRouter.get '/admin', SecurityManager.requestIsAdmin, AdminController.index webRouter.get '/admin/user', SecurityManager.requestIsAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon @@ -192,7 +192,7 @@ module.exports = class Router apiRouter.get '/status', (req,res)-> res.send("websharelatex is up") - + webRouter.get '/health_check', HealthCheckController.check webRouter.get '/health_check/redis', HealthCheckController.checkRedis diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index 54b2b2f42f..cc8e472dd1 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -9,7 +9,8 @@ block content script(type="text/javascript"). window.data = { projects: !{JSON.stringify(projects).replace(/\//g, '\\/')}, - tags: !{JSON.stringify(tags).replace(/\//g, '\\/')} + tags: !{JSON.stringify(tags).replace(/\//g, '\\/')}, + notifications: !{JSON.stringify(notifications).replace(/\//g, '\\/')} }; window.algolia = { institutions: { @@ -19,6 +20,7 @@ block content }; .content.content-alt(ng-controller="ProjectPageController") + include ./list/notifications .container .row(ng-cloak) span(ng-show="first_sign_up == 'default' || projects.length > 0") diff --git a/services/web/app/views/project/list/notifications.jade b/services/web/app/views/project/list/notifications.jade new file mode 100644 index 0000000000..e68198edb6 --- /dev/null +++ b/services/web/app/views/project/list/notifications.jade @@ -0,0 +1,18 @@ +.notifications(ng-controller="NotificationsController") + .row.row-spaced + .col-xs-12 + ul.list-unstyled.notifications-list.structured-list( + select-all-list, + ng-if="notifications.length > 0", + ng-cloak + ) + li.notification_entry.container-fluid( + ng-repeat="unreadNotification in notifications", + ) + .row + .col-xs-6 + span {{unreadNotification._id}} + .col-xs-2 + span.owner {{unreadNotification.user_id}} + .col-xs-4 + span.last-modified {{unreadNotification.templateKey}} \ No newline at end of file diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index cdfe451c1a..1c271f4c69 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -106,6 +106,8 @@ module.exports = url: "http://localhost:3036" sixpack: url: "" + notifications: + url: "http://localhost:3033" templates: user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2" @@ -143,8 +145,6 @@ module.exports = versioning: true compileTimeout: 60 compileGroup: "standard" - references: true - templates: true plans: plans = [{ planCode: "personal" diff --git a/services/web/public/coffee/main/project-list/index.coffee b/services/web/public/coffee/main/project-list/index.coffee index 82367e1a63..936228b546 100644 --- a/services/web/public/coffee/main/project-list/index.coffee +++ b/services/web/public/coffee/main/project-list/index.coffee @@ -2,6 +2,7 @@ define [ "main/project-list/project-list" "main/project-list/modal-controllers" "main/project-list/tag-controllers" + "main/project-list/notifications-controller" "main/project-list/queued-http" "main/project-list/left-hand-menu-promo-controller" ], () -> \ No newline at end of file diff --git a/services/web/public/coffee/main/project-list/notifications-controller.coffee b/services/web/public/coffee/main/project-list/notifications-controller.coffee new file mode 100644 index 0000000000..31b76dd2b5 --- /dev/null +++ b/services/web/public/coffee/main/project-list/notifications-controller.coffee @@ -0,0 +1,5 @@ +define [ + "base" +], (App) -> + + App.controller "NotificationsController", ($scope) -> \ No newline at end of file diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index 4e7efafa78..a41319d375 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -5,6 +5,7 @@ define [ App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout, sixpack) -> $scope.projects = window.data.projects $scope.tags = window.data.tags + $scope.notifications = window.data.notifications $scope.allSelected = false $scope.selectedProjects = [] $scope.filter = "all" diff --git a/services/web/test/UnitTests/coffee/Notifications/NotificationsControllerTests.coffee b/services/web/test/UnitTests/coffee/Notifications/NotificationsControllerTests.coffee new file mode 100644 index 0000000000..9069d8c193 --- /dev/null +++ b/services/web/test/UnitTests/coffee/Notifications/NotificationsControllerTests.coffee @@ -0,0 +1,39 @@ +SandboxedModule = require('sandboxed-module') +assert = require('assert') +require('chai').should() +sinon = require('sinon') +modulePath = require('path').join __dirname, '../../../../app/js/Features/Notifications/NotificationsController.js' + + +describe 'NotificationsController', -> + user_id = "123nd3ijdks" + notification_id = "123njdskj9jlk" + + beforeEach -> + @handler = + getUserNotifications: sinon.stub().callsArgWith(1) + markAsRead: sinon.stub().callsArgWith(2) + @controller = SandboxedModule.require modulePath, requires: + "./NotificationsHandler":@handler + 'logger-sharelatex': + log:-> + err:-> + @req = + params: + notification_id:notification_id + session: + user: + _id:user_id + + it 'should ask the handler for all unread notifications', (done)-> + allNotifications = [{_id: notification_id, user_id: user_id}] + @handler.getUserNotifications = sinon.stub().callsArgWith(1, null, allNotifications) + @controller.getAllUnreadNotifications @req, send:(body)=> + body.should.equal allNotifications + @handler.getUserNotifications.calledWith(user_id).should.equal true + done() + + it 'should send a delete request when a delete has been received to mark a notification', (done)-> + @controller.markNotificationAsRead @req, send:=> + @handler.markAsRead.calledWith(user_id, notification_id).should.equal true + done() diff --git a/services/web/test/UnitTests/coffee/Notifications/NotificationsHandlerTests.coffee b/services/web/test/UnitTests/coffee/Notifications/NotificationsHandlerTests.coffee new file mode 100644 index 0000000000..d134753efa --- /dev/null +++ b/services/web/test/UnitTests/coffee/Notifications/NotificationsHandlerTests.coffee @@ -0,0 +1,48 @@ +SandboxedModule = require('sandboxed-module') +assert = require('chai').assert +require('chai').should() +sinon = require('sinon') +modulePath = require('path').join __dirname, '../../../../app/js/Features/Notifications/NotificationsHandler.js' +_ = require('underscore') + + +describe 'NotificationsHandler', -> + user_id = "123nd3ijdks" + notification_id = "123njdskj9jlk" + notificationUrl = "notification.sharelatex.testing" + + beforeEach -> + @request = + post: sinon.stub().callsArgWith(1) + del: sinon.stub().callsArgWith(1) + get: sinon.stub() + @handler = SandboxedModule.require modulePath, requires: + "settings-sharelatex": apis:{notifications:{url:notificationUrl}} + "request":@request + 'logger-sharelatex': + log:-> + err:-> + + describe "getUserNotifications", -> + it 'should get unread notifications', (done)-> + stubbedNotifications = [{_id: notification_id, user_id: user_id}] + @request.get.callsArgWith(1, null, {statusCode:200}, stubbedNotifications) + @handler.getUserNotifications user_id, (err, unreadNotifications)=> + stubbedNotifications.should.deep.equal unreadNotifications + getOpts = + uri: "#{notificationUrl}/user/#{user_id}" + json:true + timeout:2000 + @request.get.calledWith(getOpts).should.equal true + done() + + it 'should return empty arrays if there are no notifications', -> + @request.get.callsArgWith(1, null, {statusCode:200}, null) + @handler.getUserNotifications user_id, (err, unreadNotifications)=> + unreadNotifications.length.should.equal 0 + + describe "markAsRead", -> + it 'should send a delete request when a delete has been received to mark a notification', (done)-> + @handler.markAsRead user_id, notification_id, => + @request.del.calledWith({uri:"#{notificationUrl}/user/#{user_id}/notification/#{notification_id}", timeout:1000}).should.equal true + done() From 2d117058374c0618f861bddb887ac1b1fd25746d Mon Sep 17 00:00:00 2001 From: Henrique Santos Date: Fri, 22 Jan 2016 03:41:22 -0200 Subject: [PATCH 2/7] fix issues in first commit --- .../Features/Project/ProjectController.coffee | 20 +++++++++++------ services/web/app/coffee/router.coffee | 22 +++++++++++-------- .../Project/ProjectControllerTests.coffee | 5 +++++ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index b3a5635f97..a1b1f9b61d 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -8,8 +8,8 @@ metrics = require('../../infrastructure/Metrics') Project = require('../../models/Project').Project User = require('../../models/User').User TagsHandler = require("../Tags/TagsHandler") -NotificationsHandler = require("../Notifications/NotificationsHandler") SubscriptionLocator = require("../Subscription/SubscriptionLocator") +NotificationsHandler = require("../Notifications/NotificationsHandler") LimitationsManager = require("../Subscription/LimitationsManager") _ = require("underscore") Settings = require("settings-sharelatex") @@ -17,6 +17,8 @@ SecurityManager = require("../../managers/SecurityManager") fs = require "fs" InactiveProjectManager = require("../InactiveData/InactiveProjectManager") ProjectUpdateHandler = require("./ProjectUpdateHandler") +ReferencesSearchHandler = require("../ReferencesSearch/ReferencesSearchHandler") + module.exports = ProjectController = @@ -173,15 +175,15 @@ module.exports = ProjectController = return res.render("general/closed", {title:"updating_site"}) if req.session.user? - user_id = req.session.user._id + user_id = req.session.user._id anonymous = false else anonymous = true user_id = 'openUser' - + project_id = req.params.Project_id logger.log project_id:project_id, "loading editor" - + async.parallel { project: (cb)-> Project.findPopulatedById project_id, cb @@ -198,7 +200,7 @@ module.exports = ProjectController = SubscriptionLocator.getUsersSubscription user_id, cb activate: (cb)-> InactiveProjectManager.reactivateProjectIfRequired project_id, cb - markAsOpened: (cb)-> + markAsOpened: (cb)-> #don't need to wait for this to complete ProjectUpdateHandler.markAsOpened project_id, -> cb() @@ -210,6 +212,7 @@ module.exports = ProjectController = user = results.user subscription = results.subscription + daysSinceLastUpdated = (new Date() - project.lastUpdated) /86400000 logger.log project_id:project_id, daysSinceLastUpdated:daysSinceLastUpdated, "got db results for loading editor" @@ -219,6 +222,10 @@ module.exports = ProjectController = if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt? allowedFreeTrial = !!subscription.freeTrial.allowed || true + + # HACK: don't do it for now + ReferencesSearchHandler.indexProjectReferences project, -> # don't need to wait on this + logger.log project_id:project_id, "rendering editor page" res.render 'project/editor', title: project.name @@ -315,5 +322,4 @@ do generateThemeList = () -> for file in files if file.slice(-2) == "js" and file.match(/^theme-/) cleanName = file.slice(0,-3).slice(6) - THEME_LIST.push cleanName - + THEME_LIST.push cleanName \ No newline at end of file diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index f40d629510..a12e6d9d4b 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -38,6 +38,7 @@ RateLimiterMiddlewear = require('./Features/Security/RateLimiterMiddlewear') RealTimeProxyRouter = require('./Features/RealTimeProxy/RealTimeProxyRouter') InactiveProjectController = require("./Features/InactiveData/InactiveProjectController") ContactRouter = require("./Features/Contacts/ContactRouter") +ReferencesSearchController = require('./Features/ReferencesSearch/ReferencesSearchController') logger = require("logger-sharelatex") _ = require("underscore") @@ -47,7 +48,7 @@ module.exports = class Router if !Settings.allowPublicAccess webRouter.all '*', AuthenticationController.requireGlobalLogin - + webRouter.get '/login', UserPagesController.loginPage AuthenticationController.addEndpointToLoginWhitelist '/login' @@ -68,18 +69,18 @@ module.exports = class Router StaticPagesRouter.apply(webRouter, apiRouter) RealTimeProxyRouter.apply(webRouter, apiRouter) ContactRouter.apply(webRouter, apiRouter) - + Modules.applyRouter(webRouter, apiRouter) if Settings.enableSubscriptions webRouter.get '/user/bonus', AuthenticationController.requireLogin(), ReferalMiddleware.getUserReferalId, ReferalController.bonus - + webRouter.get '/blog', BlogController.getIndexPage webRouter.get '/blog/*', BlogController.getPage - + webRouter.get '/user/activate', UserPagesController.activateAccountPage - + webRouter.get '/user/settings', AuthenticationController.requireLogin(), UserPagesController.settingsPage webRouter.post '/user/settings', AuthenticationController.requireLogin(), UserController.updateUserSettings webRouter.post '/user/password/update', AuthenticationController.requireLogin(), UserController.changePassword @@ -162,7 +163,7 @@ module.exports = class Router apiRouter.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate apiRouter.delete '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate - + apiRouter.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents apiRouter.delete '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents @@ -171,9 +172,12 @@ module.exports = class Router webRouter.get "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.getMessages webRouter.post "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.sendMessage - + webRouter.get /learn(\/.*)?/, WikiController.getPage + webRouter.post "/project/:Project_id/references", SecurityManager.requestCanAccessProject, ReferencesSearchController.indexFile + webRouter.get "/project/:Project_id/references/keys", SecurityManager.requestCanAccessProject, ReferencesSearchController.getKeys + #Admin Stuff webRouter.get '/admin', SecurityManager.requestIsAdmin, AdminController.index webRouter.get '/admin/user', SecurityManager.requestIsAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon @@ -192,7 +196,7 @@ module.exports = class Router apiRouter.get '/status', (req,res)-> res.send("websharelatex is up") - + webRouter.get '/health_check', HealthCheckController.check webRouter.get '/health_check/redis', HealthCheckController.checkRedis @@ -228,4 +232,4 @@ module.exports = class Router logger.error err: req.body.error, meta: req.body.meta, "client side error" res.sendStatus(204) - webRouter.get '*', ErrorController.notFound + webRouter.get '*', ErrorController.notFound \ No newline at end of file diff --git a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee index 7fe296d4d0..050a61f207 100644 --- a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee @@ -33,6 +33,8 @@ describe "ProjectController", -> userHasSubscriptionOrIsGroupMember: sinon.stub() @TagsHandler = getAllTags: sinon.stub() + @NotificationsHandler = + getUserNotifications: sinon.stub() @ProjectModel = findAllUsersProjects: sinon.stub() findPopulatedById: sinon.stub() @@ -60,6 +62,7 @@ describe "ProjectController", -> "../Subscription/SubscriptionLocator": @SubscriptionLocator "../Subscription/LimitationsManager": @LimitationsManager "../Tags/TagsHandler":@TagsHandler + "../Notifications/NotificationsHandler":@NotificationsHandler '../../models/Project': Project:@ProjectModel "../../models/User":User:@UserModel "../../managers/SecurityManager":@SecurityManager @@ -198,6 +201,7 @@ describe "ProjectController", -> beforeEach -> @tags = [{name:1, project_ids:["1","2","3"]}, {name:2, project_ids:["a","1"]}, {name:3, project_ids:["a", "b", "c", "d"]}] + @notifications = [{_id:'1',user_id:'2',templateKey:'3',messageOpts:'4',key:'5'}] @projects = [{lastUpdated:1, _id:1, owner_ref: "user-1"}, {lastUpdated:2, _id:2, owner_ref: "user-2"}] @collabertions = [{lastUpdated:5, _id:5, owner_ref: "user-1"}] @readOnly = [{lastUpdated:3, _id:3, owner_ref: "user-1"}] @@ -213,6 +217,7 @@ describe "ProjectController", -> @LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, false) @TagsHandler.getAllTags.callsArgWith(1, null, @tags, {}) + @NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {}) @ProjectModel.findAllUsersProjects.callsArgWith(2, null, @projects, @collabertions, @readOnly) it "should render the project/list page", (done)-> From 25f9bd30b6d6228d8ca756cabc4e567d287d3774 Mon Sep 17 00:00:00 2001 From: Henrique Santos Date: Fri, 22 Jan 2016 03:54:38 -0200 Subject: [PATCH 3/7] fix config file --- services/web/config/settings.defaults.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 1c271f4c69..516dca37dc 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -145,6 +145,8 @@ module.exports = versioning: true compileTimeout: 60 compileGroup: "standard" + references: true + templates: true plans: plans = [{ planCode: "personal" From 9429e49cf829d40a3afc6ef8ee45d7da0bc316f5 Mon Sep 17 00:00:00 2001 From: Henrique Santos Date: Fri, 22 Jan 2016 18:08:39 -0200 Subject: [PATCH 4/7] finishing frontend --- .../NotificationsController.coffee | 4 +++ .../Notifications/NotificationsHandler.coffee | 2 +- .../Features/Project/ProjectController.coffee | 4 ++- services/web/app/views/project/list.jade | 3 +- .../app/views/project/list/notifications.jade | 35 ++++++++++--------- .../notifications-controller.coffee | 14 +++++++- .../NotificationsControllerTests.coffee | 4 +++ .../Project/ProjectControllerTests.coffee | 2 ++ 8 files changed, 47 insertions(+), 21 deletions(-) diff --git a/services/web/app/coffee/Features/Notifications/NotificationsController.coffee b/services/web/app/coffee/Features/Notifications/NotificationsController.coffee index 7137dfa986..c4931f5327 100644 --- a/services/web/app/coffee/Features/Notifications/NotificationsController.coffee +++ b/services/web/app/coffee/Features/Notifications/NotificationsController.coffee @@ -1,10 +1,14 @@ NotificationsHandler = require("./NotificationsHandler") logger = require("logger-sharelatex") +_ = require("underscore") module.exports = getAllUnreadNotifications: (req, res)-> NotificationsHandler.getUserNotifications req.session.user._id, (err, unreadNotifications)-> + unreadNotifications = _.map unreadNotifications, (notification)-> + notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts) + return notification res.send(unreadNotifications) markNotificationAsRead: (req, res)-> diff --git a/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee index 1cf11af1f9..b54fd95b3f 100644 --- a/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee +++ b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee @@ -15,7 +15,7 @@ module.exports = if err? or statusCode != 200 e = new Error("something went wrong getting notifications, #{err}, #{statusCode}") logger.err err:err - callback(e, []) + callback(null, []) else if !unreadNotifications? unreadNotifications = [] diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index a1b1f9b61d..379cb6ef74 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -142,7 +142,9 @@ module.exports = ProjectController = return next(err) logger.log results:results, user_id:user_id, "rendering project list" tags = results.tags[0] - notifications = results.notifications + notifications = require("underscore").map results.notifications, (notification)-> + notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts) + return notification projects = ProjectController._buildProjectList results.projects[0], results.projects[1], results.projects[2] user = results.user ProjectController._injectProjectOwners projects, (error, projects) -> diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index cc8e472dd1..08260219e3 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -20,8 +20,9 @@ block content }; .content.content-alt(ng-controller="ProjectPageController") - include ./list/notifications .container + .row(ng-cloak) + include ./list/notifications .row(ng-cloak) span(ng-show="first_sign_up == 'default' || projects.length > 0") aside.col-md-2.col-xs-3 diff --git a/services/web/app/views/project/list/notifications.jade b/services/web/app/views/project/list/notifications.jade index e68198edb6..531d02328e 100644 --- a/services/web/app/views/project/list/notifications.jade +++ b/services/web/app/views/project/list/notifications.jade @@ -1,18 +1,19 @@ -.notifications(ng-controller="NotificationsController") - .row.row-spaced - .col-xs-12 - ul.list-unstyled.notifications-list.structured-list( - select-all-list, - ng-if="notifications.length > 0", - ng-cloak - ) - li.notification_entry.container-fluid( - ng-repeat="unreadNotification in notifications", +span(ng-controller="NotificationsController") + aside.col-md-2.col-xs-3 + .col-md-10.col-xs-9 + .row + .col-xs-12(ng-cloak) + ul.list-unstyled.notifications-list( + ng-if="notifications.length > 0", + ng-cloak ) - .row - .col-xs-6 - span {{unreadNotification._id}} - .col-xs-2 - span.owner {{unreadNotification.user_id}} - .col-xs-4 - span.last-modified {{unreadNotification.templateKey}} \ No newline at end of file + li.notification_entry( + ng-repeat="unreadNotification in notifications", + ) + .row(ng-hide="unreadNotification.hide") + .col-xs-12 + .alert.alert-info + span {{unreadNotification.html}} + button(ng-click="dismiss(unreadNotification)").close.pull-right + span(aria-hidden="true") × + span.sr-only #{translate("close")} diff --git a/services/web/public/coffee/main/project-list/notifications-controller.coffee b/services/web/public/coffee/main/project-list/notifications-controller.coffee index 31b76dd2b5..36a725f778 100644 --- a/services/web/public/coffee/main/project-list/notifications-controller.coffee +++ b/services/web/public/coffee/main/project-list/notifications-controller.coffee @@ -2,4 +2,16 @@ define [ "base" ], (App) -> - App.controller "NotificationsController", ($scope) -> \ No newline at end of file + App.controller "NotificationsController", ($scope, $http) -> + for notification in $scope.notifications + notification.hide = false + + $scope.dismiss = (notification) -> + $http({ + url: "/notifications/#{notification._id}" + method: "DELETE" + headers: + "X-Csrf-Token": window.csrfToken + }) + .success (data) -> + notification.hide = true diff --git a/services/web/test/UnitTests/coffee/Notifications/NotificationsControllerTests.coffee b/services/web/test/UnitTests/coffee/Notifications/NotificationsControllerTests.coffee index 9069d8c193..083257e62c 100644 --- a/services/web/test/UnitTests/coffee/Notifications/NotificationsControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Notifications/NotificationsControllerTests.coffee @@ -15,6 +15,8 @@ describe 'NotificationsController', -> markAsRead: sinon.stub().callsArgWith(2) @controller = SandboxedModule.require modulePath, requires: "./NotificationsHandler":@handler + "underscore":@underscore = + map:(arr)-> return arr 'logger-sharelatex': log:-> err:-> @@ -24,6 +26,8 @@ describe 'NotificationsController', -> session: user: _id:user_id + i18n: + translate:-> it 'should ask the handler for all unread notifications', (done)-> allNotifications = [{_id: notification_id, user_id: user_id}] diff --git a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee index 050a61f207..3ecb31ba68 100644 --- a/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee @@ -81,6 +81,8 @@ describe "ProjectController", -> user: @user body: projectName: @projectName + i18n: + translate:-> @res = locals: jsPath:"js path here" From 9d61a58710b2ff5bb8f25bca8d7fc732308727a1 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 4 Feb 2016 12:11:45 +0000 Subject: [PATCH 5/7] slighly change layout of notifications & insert html via angular --- services/web/app/views/project/list.jade | 4 +-- .../app/views/project/list/notifications.jade | 30 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index 08260219e3..1c80b9a7a2 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -21,14 +21,14 @@ block content .content.content-alt(ng-controller="ProjectPageController") .container - .row(ng-cloak) - include ./list/notifications + .row(ng-cloak) span(ng-show="first_sign_up == 'default' || projects.length > 0") aside.col-md-2.col-xs-3 include ./list/side-bar .col-md-10.col-xs-9 + include ./list/notifications include ./list/project-list span(ng-if="first_sign_up == 'minimial' && projects.length == 0") diff --git a/services/web/app/views/project/list/notifications.jade b/services/web/app/views/project/list/notifications.jade index 531d02328e..209d4477fc 100644 --- a/services/web/app/views/project/list/notifications.jade +++ b/services/web/app/views/project/list/notifications.jade @@ -1,19 +1,17 @@ span(ng-controller="NotificationsController") - aside.col-md-2.col-xs-3 - .col-md-10.col-xs-9 + .col-md-12 .row - .col-xs-12(ng-cloak) - ul.list-unstyled.notifications-list( - ng-if="notifications.length > 0", - ng-cloak + ul.list-unstyled.notifications-list( + ng-if="notifications.length > 0", + ng-cloak + ) + li.notification_entry( + ng-repeat="unreadNotification in notifications", ) - li.notification_entry( - ng-repeat="unreadNotification in notifications", - ) - .row(ng-hide="unreadNotification.hide") - .col-xs-12 - .alert.alert-info - span {{unreadNotification.html}} - button(ng-click="dismiss(unreadNotification)").close.pull-right - span(aria-hidden="true") × - span.sr-only #{translate("close")} + .row(ng-hide="unreadNotification.hide") + .col-xs-12 + .alert.alert-warning + span(ng-bind-html="unreadNotification.html") + button(ng-click="dismiss(unreadNotification)").close.pull-right + span(aria-hidden="true") × + span.sr-only #{translate("close")} From f136486f4bb516fbea28d78232a924e8fc169c83 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 4 Feb 2016 14:28:31 +0000 Subject: [PATCH 6/7] cleanup layout of notifications --- .../Notifications/NotificationsHandler.coffee | 2 +- .../app/views/project/list/notifications.jade | 32 +++++++++---------- services/web/config/settings.defaults.coffee | 2 +- .../public/stylesheets/app/project-list.less | 7 ++++ 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee index b54fd95b3f..ef20351c90 100644 --- a/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee +++ b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee @@ -14,7 +14,7 @@ module.exports = statusCode = if res? then res.statusCode else 500 if err? or statusCode != 200 e = new Error("something went wrong getting notifications, #{err}, #{statusCode}") - logger.err err:err + logger.err err:err, "something went wrong getting notifications" callback(null, []) else if !unreadNotifications? diff --git a/services/web/app/views/project/list/notifications.jade b/services/web/app/views/project/list/notifications.jade index 209d4477fc..8e0ee56f43 100644 --- a/services/web/app/views/project/list/notifications.jade +++ b/services/web/app/views/project/list/notifications.jade @@ -1,17 +1,15 @@ -span(ng-controller="NotificationsController") - .col-md-12 - .row - ul.list-unstyled.notifications-list( - ng-if="notifications.length > 0", - ng-cloak - ) - li.notification_entry( - ng-repeat="unreadNotification in notifications", - ) - .row(ng-hide="unreadNotification.hide") - .col-xs-12 - .alert.alert-warning - span(ng-bind-html="unreadNotification.html") - button(ng-click="dismiss(unreadNotification)").close.pull-right - span(aria-hidden="true") × - span.sr-only #{translate("close")} +span(ng-controller="NotificationsController").userNotifications + ul.list-unstyled.notifications-list( + ng-if="notifications.length > 0", + ng-cloak + ) + li.notification_entry( + ng-repeat="unreadNotification in notifications", + ) + .row(ng-hide="unreadNotification.hide") + .col-xs-12 + .alert.alert-warning + span(ng-bind-html="unreadNotification.html") + button(ng-click="dismiss(unreadNotification)").close.pull-right + span(aria-hidden="true") × + span.sr-only #{translate("close")} diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 516dca37dc..ad0423c7e3 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -107,7 +107,7 @@ module.exports = sixpack: url: "" notifications: - url: "http://localhost:3033" + url: "http://localhost:3042" templates: user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2" diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 7cc23a3d8a..877f1881f9 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -28,6 +28,13 @@ } } +.userNotifications { + ul { + margin-bottom:0px; + } + +} + ul.folders-menu { margin: 0; .subdued { From de0589b0518b3cb836ef54b21bfb3151bab1e7a1 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 5 Feb 2016 14:13:38 +0000 Subject: [PATCH 7/7] added notifications calls for sending to api and mark as read not creating it yet --- .../Notifications/NotificationsBuilder.coffee | 16 +++++++++++ .../Notifications/NotificationsHandler.coffee | 21 ++++++++++++++ .../SubscriptionDomainHandler.coffee | 14 ++++++---- .../NotificationsHandlerTests.coffee | 28 +++++++++++++++++-- 4 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 services/web/app/coffee/Features/Notifications/NotificationsBuilder.coffee diff --git a/services/web/app/coffee/Features/Notifications/NotificationsBuilder.coffee b/services/web/app/coffee/Features/Notifications/NotificationsBuilder.coffee new file mode 100644 index 0000000000..c68e265920 --- /dev/null +++ b/services/web/app/coffee/Features/Notifications/NotificationsBuilder.coffee @@ -0,0 +1,16 @@ + +NotificationsHandler = require("./NotificationsHandler") + +module.exports = + + groupPlan: (user, licence)-> + key : "join-sub-#{licence.subscription_id}" + + create: (callback = ->)-> + messageOpts = + groupName: licence.name + subscription_id: licence.subscription_id + NotificationsHandler.createNotification user._id, key, "joinSubscriptionInvite", messageOpts, callback + + read: (callback = ->)-> + NotificationsHandler.markAsReadWithKey user._id, key, callback diff --git a/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee index ef20351c90..d621040f69 100644 --- a/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee +++ b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee @@ -21,6 +21,27 @@ module.exports = unreadNotifications = [] callback(null, unreadNotifications) + createNotification: (user_id, key, templateKey, messageOpts, callback)-> + opts = + uri: "#{settings.apis.notifications.url}/user/#{user_id}" + timeout: 1000 + json: { + key:key + messageOpts:messageOpts + templateKey:templateKey + } + request.post opts, callback + + markAsReadWithKey: (user_id, key, callback)-> + opts = + uri: "#{settings.apis.notifications.url}/user/#{user_id}" + timeout: 1000 + json: { + key:key + } + request.del opts, callback + + markAsRead: (user_id, notification_id, callback)-> opts = uri: "#{settings.apis.notifications.url}/user/#{user_id}/notification/#{notification_id}" diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionDomainHandler.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionDomainHandler.coffee index b57c3492f6..0e655c45fc 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionDomainHandler.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionDomainHandler.coffee @@ -1,3 +1,4 @@ +NotificationsBuilder = require("../Notifications/NotificationsBuilder") async = require("async") _ = require("underscore") settings = require("settings-sharelatex") @@ -7,17 +8,18 @@ _s = require("underscore.string") module.exports = SubscriptionDomainHandler = - getLicenceUserCanJoin: (user, callback)-> + getLicenceUserCanJoin: (user)-> licence = SubscriptionDomainHandler._findDomainLicence(user.email) - if licence? - callback null, licence - else - callback() + return licence attemptToJoinGroup: (user, callback)-> licence = SubscriptionDomainHandler._findDomainLicence(user.email) if licence? and user.emailVerified - SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, callback + SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, (err)-> + if err? + logger.err err:err, "error adding user to group" + return callback(err) + NotificationsBuilder.groupPlan(user, licence).read() else callback "user not verified" diff --git a/services/web/test/UnitTests/coffee/Notifications/NotificationsHandlerTests.coffee b/services/web/test/UnitTests/coffee/Notifications/NotificationsHandlerTests.coffee index d134753efa..d63d1ca4a1 100644 --- a/services/web/test/UnitTests/coffee/Notifications/NotificationsHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Notifications/NotificationsHandlerTests.coffee @@ -42,7 +42,31 @@ describe 'NotificationsHandler', -> unreadNotifications.length.should.equal 0 describe "markAsRead", -> + beforeEach -> + @key = "some key here" + it 'should send a delete request when a delete has been received to mark a notification', (done)-> - @handler.markAsRead user_id, notification_id, => - @request.del.calledWith({uri:"#{notificationUrl}/user/#{user_id}/notification/#{notification_id}", timeout:1000}).should.equal true + @handler.markAsReadWithKey user_id, @key, => + opts = + uri: "#{notificationUrl}/user/#{user_id}" + json: + key:@key + timeout:1000 + @request.del.calledWith(opts).should.equal true done() + + + describe "createNotification", -> + beforeEach -> + @key = "some key here" + @messageOpts = {value:12344} + @templateKey = "renderThisHtml" + + it "should post the message over", (done)-> + @handler.createNotification user_id, @key, @templateKey, @messageOpts, => + args = @request.post.args[0][0] + args.uri.should.equal "#{notificationUrl}/user/#{user_id}" + args.timeout.should.equal 1000 + expectedJson = {key:@key, templateKey:@templateKey, messageOpts:@messageOpts} + assert.deepEqual(args.json, expectedJson) + done() \ No newline at end of file