mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
commit
bc80402eda
31 changed files with 4274 additions and 747 deletions
|
@ -5,5 +5,3 @@ gitrev
|
|||
.npm
|
||||
.nvmrc
|
||||
nodemon.json
|
||||
app.js
|
||||
**/js/*
|
||||
|
|
65
services/contacts/.eslintrc
Normal file
65
services/contacts/.eslintrc
Normal file
|
@ -0,0 +1,65 @@
|
|||
// this file was auto-generated, do not edit it directly.
|
||||
// instead run bin/update_build_scripts from
|
||||
// https://github.com/sharelatex/sharelatex-dev-environment
|
||||
// Version: 1.3.5
|
||||
{
|
||||
"extends": [
|
||||
"standard",
|
||||
"prettier",
|
||||
"prettier/standard"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2017
|
||||
},
|
||||
"plugins": [
|
||||
"mocha",
|
||||
"chai-expect",
|
||||
"chai-friendly"
|
||||
],
|
||||
"env": {
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
// Swap the no-unused-expressions rule with a more chai-friendly one
|
||||
"no-unused-expressions": 0,
|
||||
"chai-friendly/no-unused-expressions": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
// Test specific rules
|
||||
"files": ["test/**/*.js"],
|
||||
"globals": {
|
||||
"expect": true
|
||||
},
|
||||
"rules": {
|
||||
// mocha-specific rules
|
||||
"mocha/handle-done-callback": "error",
|
||||
"mocha/no-exclusive-tests": "error",
|
||||
"mocha/no-global-tests": "error",
|
||||
"mocha/no-identical-title": "error",
|
||||
"mocha/no-nested-tests": "error",
|
||||
"mocha/no-pending-tests": "error",
|
||||
"mocha/no-skipped-tests": "error",
|
||||
"mocha/no-mocha-arrows": "error",
|
||||
|
||||
// chai-specific rules
|
||||
"chai-expect/missing-assertion": "error",
|
||||
"chai-expect/terminating-properties": "error",
|
||||
|
||||
// prefer-arrow-callback applies to all callbacks, not just ones in mocha tests.
|
||||
// we don't enforce this at the top-level - just in tests to manage `this` scope
|
||||
// based on mocha's context mechanism
|
||||
"mocha/prefer-arrow-callback": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Backend specific rules
|
||||
"files": ["app/**/*.js", "app.js", "index.js"],
|
||||
"rules": {
|
||||
// don't allow console.log in backend code
|
||||
"no-console": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
5
services/contacts/.gitignore
vendored
5
services/contacts/.gitignore
vendored
|
@ -1,7 +1,2 @@
|
|||
node_modules
|
||||
app/js/
|
||||
test/unit/js
|
||||
test/acceptance/js
|
||||
app.js
|
||||
**/*.map
|
||||
forever
|
||||
|
|
8
services/contacts/.prettierrc
Normal file
8
services/contacts/.prettierrc
Normal file
|
@ -0,0 +1,8 @@
|
|||
# This file was auto-generated, do not edit it directly.
|
||||
# Instead run bin/update_build_scripts from
|
||||
# https://github.com/sharelatex/sharelatex-dev-environment
|
||||
# Version: 1.3.5
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
|
@ -17,7 +17,6 @@ RUN npm install --quiet
|
|||
COPY . /app
|
||||
|
||||
|
||||
RUN npm run compile:all
|
||||
|
||||
FROM base
|
||||
|
||||
|
|
7
services/contacts/Jenkinsfile
vendored
7
services/contacts/Jenkinsfile
vendored
|
@ -37,6 +37,13 @@ pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
stage('Linting') {
|
||||
steps {
|
||||
sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make format'
|
||||
sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make lint'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Unit Tests') {
|
||||
steps {
|
||||
sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_unit'
|
||||
|
|
|
@ -16,12 +16,17 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
|
|||
clean:
|
||||
docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
||||
docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
||||
rm -f app.js
|
||||
rm -rf app/js
|
||||
rm -rf test/unit/js
|
||||
rm -rf test/acceptance/js
|
||||
|
||||
test: test_unit test_acceptance
|
||||
format:
|
||||
$(DOCKER_COMPOSE) run --rm test_unit npm run format
|
||||
|
||||
format_fix:
|
||||
$(DOCKER_COMPOSE) run --rm test_unit npm run format:fix
|
||||
|
||||
lint:
|
||||
$(DOCKER_COMPOSE) run --rm test_unit npm run lint
|
||||
|
||||
test: format lint test_unit test_acceptance
|
||||
|
||||
test_unit:
|
||||
@[ ! -d test/unit ] && echo "contacts has no unit tests" || $(DOCKER_COMPOSE) run --rm test_unit
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
Metrics = require "metrics-sharelatex"
|
||||
Metrics.initialize("contacts")
|
||||
|
||||
Settings = require "settings-sharelatex"
|
||||
logger = require "logger-sharelatex"
|
||||
express = require "express"
|
||||
bodyParser = require "body-parser"
|
||||
Errors = require "./app/js/Errors"
|
||||
HttpController = require "./app/js/HttpController"
|
||||
|
||||
Path = require "path"
|
||||
|
||||
|
||||
logger.initialize("contacts")
|
||||
Metrics.event_loop?.monitor(logger)
|
||||
|
||||
app = express()
|
||||
|
||||
app.use Metrics.http.monitor(logger)
|
||||
|
||||
Metrics.injectMetricsRoute(app)
|
||||
|
||||
app.get '/user/:user_id/contacts', HttpController.getContacts
|
||||
app.post '/user/:user_id/contacts', bodyParser.json(limit: "2mb"), HttpController.addContact
|
||||
|
||||
app.get '/status', (req, res)->
|
||||
res.send('contacts is alive')
|
||||
|
||||
app.use (error, req, res, next) ->
|
||||
logger.error err: error, "request errored"
|
||||
if error instanceof Errors.NotFoundError
|
||||
res.send 404
|
||||
else
|
||||
res.send(500, "Oops, something went wrong")
|
||||
|
||||
port = Settings.internal.contacts.port
|
||||
host = Settings.internal.contacts.host
|
||||
|
||||
|
||||
if !module.parent # Called directly
|
||||
app.listen port, host, (error) ->
|
||||
throw error if error?
|
||||
logger.info "contacts starting up, listening on #{host}:#{port}"
|
||||
|
||||
module.exports = app
|
59
services/contacts/app.js
Normal file
59
services/contacts/app.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const Metrics = require('metrics-sharelatex')
|
||||
Metrics.initialize('contacts')
|
||||
|
||||
const Settings = require('settings-sharelatex')
|
||||
const logger = require('logger-sharelatex')
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
const Errors = require('./app/js/Errors')
|
||||
const HttpController = require('./app/js/HttpController')
|
||||
|
||||
logger.initialize('contacts')
|
||||
if (Metrics.event_loop != null) {
|
||||
Metrics.event_loop.monitor(logger)
|
||||
}
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use(Metrics.http.monitor(logger))
|
||||
|
||||
Metrics.injectMetricsRoute(app)
|
||||
|
||||
app.get('/user/:user_id/contacts', HttpController.getContacts)
|
||||
app.post(
|
||||
'/user/:user_id/contacts',
|
||||
bodyParser.json({ limit: '2mb' }),
|
||||
HttpController.addContact
|
||||
)
|
||||
|
||||
app.get('/status', (req, res) => res.send('contacts is alive'))
|
||||
|
||||
app.use(function(error, req, res, next) {
|
||||
logger.error({ err: error }, 'request errored')
|
||||
if (error instanceof Errors.NotFoundError) {
|
||||
return res.send(404)
|
||||
} else {
|
||||
return res.send(500, 'Oops, something went wrong')
|
||||
}
|
||||
})
|
||||
|
||||
const { port } = Settings.internal.contacts
|
||||
const { host } = Settings.internal.contacts
|
||||
|
||||
if (!module.parent) {
|
||||
// Called directly
|
||||
app.listen(port, host, function(error) {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
return logger.info(`contacts starting up, listening on ${host}:${port}`)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = app
|
|
@ -1,38 +0,0 @@
|
|||
{db, ObjectId} = require "./mongojs"
|
||||
logger = require('logger-sharelatex')
|
||||
metrics = require('metrics-sharelatex')
|
||||
|
||||
module.exports = ContactManager =
|
||||
touchContact: (user_id, contact_id, callback = (error) ->) ->
|
||||
try
|
||||
user_id = ObjectId(user_id.toString())
|
||||
catch error
|
||||
return callback error
|
||||
|
||||
update = { $set: {}, $inc: {} }
|
||||
update.$inc["contacts.#{contact_id}.n"] = 1
|
||||
update.$set["contacts.#{contact_id}.ts"] = new Date()
|
||||
|
||||
db.contacts.update({
|
||||
user_id: user_id
|
||||
}, update, {
|
||||
upsert: true
|
||||
}, callback)
|
||||
|
||||
getContacts: (user_id, callback = (error) ->) ->
|
||||
try
|
||||
user_id = ObjectId(user_id.toString())
|
||||
catch error
|
||||
return callback error
|
||||
|
||||
db.contacts.findOne {
|
||||
user_id: user_id
|
||||
}, (error, user) ->
|
||||
return callback(error) if error?
|
||||
callback null, user?.contacts
|
||||
|
||||
[
|
||||
'touchContact',
|
||||
'getContacts',
|
||||
].map (method) ->
|
||||
metrics.timeAsyncMethod(ContactManager, method, 'mongo.ContactManager', logger)
|
|
@ -1,10 +0,0 @@
|
|||
NotFoundError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = "NotFoundError"
|
||||
error.__proto__ = NotFoundError.prototype
|
||||
return error
|
||||
NotFoundError.prototype.__proto__ = Error.prototype
|
||||
|
||||
module.exports = Errors =
|
||||
NotFoundError: NotFoundError
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
ContactManager = require "./ContactManager"
|
||||
logger = require "logger-sharelatex"
|
||||
|
||||
module.exports = HttpController =
|
||||
addContact: (req, res, next) ->
|
||||
{user_id} = req.params
|
||||
{contact_id} = req.body
|
||||
|
||||
if !contact_id? or contact_id == ""
|
||||
res.status(400).send("contact_id should be a non-blank string")
|
||||
return
|
||||
|
||||
logger.log {user_id, contact_id}, "adding contact"
|
||||
|
||||
ContactManager.touchContact user_id, contact_id, (error) ->
|
||||
return next(error) if error?
|
||||
ContactManager.touchContact contact_id, user_id, (error) ->
|
||||
return next(error) if error?
|
||||
res.status(204).end()
|
||||
|
||||
CONTACT_LIMIT: 50
|
||||
getContacts: (req, res, next) ->
|
||||
{user_id} = req.params
|
||||
|
||||
if req.query?.limit?
|
||||
limit = parseInt(req.query.limit, 10)
|
||||
else
|
||||
limit = HttpController.CONTACT_LIMIT
|
||||
limit = Math.min(limit, HttpController.CONTACT_LIMIT)
|
||||
|
||||
logger.log {user_id}, "getting contacts"
|
||||
|
||||
ContactManager.getContacts user_id, (error, contact_dict) ->
|
||||
return next(error) if error?
|
||||
|
||||
contacts = []
|
||||
for user_id, data of (contact_dict or {})
|
||||
contacts.push {
|
||||
user_id: user_id
|
||||
n: data.n
|
||||
ts: data.ts
|
||||
}
|
||||
|
||||
HttpController._sortContacts contacts
|
||||
contacts = contacts.slice(0, limit)
|
||||
contact_ids = contacts.map (contact) -> contact.user_id
|
||||
|
||||
res.status(200).send({
|
||||
contact_ids: contact_ids
|
||||
})
|
||||
|
||||
_sortContacts: (contacts) ->
|
||||
contacts.sort (a, b) ->
|
||||
# Sort by decreasing count, descreasing timestamp.
|
||||
# I.e. biggest count, and most recent at front.
|
||||
if a.n > b.n
|
||||
return -1
|
||||
else if a.n < b.n
|
||||
return 1
|
||||
else
|
||||
if a.ts > b.ts
|
||||
return -1
|
||||
else if a.ts < b.ts
|
||||
return 1
|
||||
else
|
||||
return 0
|
|
@ -1,7 +0,0 @@
|
|||
Settings = require "settings-sharelatex"
|
||||
mongojs = require "mongojs"
|
||||
db = mongojs(Settings.mongo.url, ["contacts"])
|
||||
module.exports =
|
||||
db: db
|
||||
ObjectId: mongojs.ObjectId
|
||||
|
77
services/contacts/app/js/ContactManager.js
Normal file
77
services/contacts/app/js/ContactManager.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
/* eslint-disable
|
||||
camelcase,
|
||||
handle-callback-err,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let ContactManager
|
||||
const { db, ObjectId } = require('./mongojs')
|
||||
const logger = require('logger-sharelatex')
|
||||
const metrics = require('metrics-sharelatex')
|
||||
|
||||
module.exports = ContactManager = {
|
||||
touchContact(user_id, contact_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(error) {}
|
||||
}
|
||||
try {
|
||||
user_id = ObjectId(user_id.toString())
|
||||
} catch (error1) {
|
||||
const error = error1
|
||||
return callback(error)
|
||||
}
|
||||
|
||||
const update = { $set: {}, $inc: {} }
|
||||
update.$inc[`contacts.${contact_id}.n`] = 1
|
||||
update.$set[`contacts.${contact_id}.ts`] = new Date()
|
||||
|
||||
return db.contacts.update(
|
||||
{
|
||||
user_id
|
||||
},
|
||||
update,
|
||||
{
|
||||
upsert: true
|
||||
},
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
getContacts(user_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(error) {}
|
||||
}
|
||||
try {
|
||||
user_id = ObjectId(user_id.toString())
|
||||
} catch (error1) {
|
||||
const error = error1
|
||||
return callback(error)
|
||||
}
|
||||
|
||||
return db.contacts.findOne(
|
||||
{
|
||||
user_id
|
||||
},
|
||||
function(error, user) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return callback(null, user != null ? user.contacts : undefined)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
;['touchContact', 'getContacts'].map(method =>
|
||||
metrics.timeAsyncMethod(
|
||||
ContactManager,
|
||||
method,
|
||||
'mongo.ContactManager',
|
||||
logger
|
||||
)
|
||||
)
|
16
services/contacts/app/js/Errors.js
Normal file
16
services/contacts/app/js/Errors.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* eslint-disable
|
||||
no-proto,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
let Errors
|
||||
var NotFoundError = function(message) {
|
||||
const error = new Error(message)
|
||||
error.name = 'NotFoundError'
|
||||
error.__proto__ = NotFoundError.prototype
|
||||
return error
|
||||
}
|
||||
NotFoundError.prototype.__proto__ = Error.prototype
|
||||
|
||||
module.exports = Errors = { NotFoundError }
|
100
services/contacts/app/js/HttpController.js
Normal file
100
services/contacts/app/js/HttpController.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
/* eslint-disable
|
||||
camelcase,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let HttpController
|
||||
const ContactManager = require('./ContactManager')
|
||||
const logger = require('logger-sharelatex')
|
||||
|
||||
module.exports = HttpController = {
|
||||
addContact(req, res, next) {
|
||||
const { user_id } = req.params
|
||||
const { contact_id } = req.body
|
||||
|
||||
if (contact_id == null || contact_id === '') {
|
||||
res.status(400).send('contact_id should be a non-blank string')
|
||||
return
|
||||
}
|
||||
|
||||
logger.log({ user_id, contact_id }, 'adding contact')
|
||||
|
||||
return ContactManager.touchContact(user_id, contact_id, function(error) {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
return ContactManager.touchContact(contact_id, user_id, function(error) {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
return res.status(204).end()
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
CONTACT_LIMIT: 50,
|
||||
getContacts(req, res, next) {
|
||||
let limit
|
||||
let { user_id } = req.params
|
||||
|
||||
if ((req.query != null ? req.query.limit : undefined) != null) {
|
||||
limit = parseInt(req.query.limit, 10)
|
||||
} else {
|
||||
limit = HttpController.CONTACT_LIMIT
|
||||
}
|
||||
limit = Math.min(limit, HttpController.CONTACT_LIMIT)
|
||||
|
||||
logger.log({ user_id }, 'getting contacts')
|
||||
|
||||
return ContactManager.getContacts(user_id, function(error, contact_dict) {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
|
||||
let contacts = []
|
||||
const object = contact_dict || {}
|
||||
for (user_id in object) {
|
||||
const data = object[user_id]
|
||||
contacts.push({
|
||||
user_id,
|
||||
n: data.n,
|
||||
ts: data.ts
|
||||
})
|
||||
}
|
||||
|
||||
HttpController._sortContacts(contacts)
|
||||
contacts = contacts.slice(0, limit)
|
||||
const contact_ids = contacts.map(contact => contact.user_id)
|
||||
|
||||
return res.status(200).send({
|
||||
contact_ids
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
_sortContacts(contacts) {
|
||||
return contacts.sort(function(a, b) {
|
||||
// Sort by decreasing count, descreasing timestamp.
|
||||
// I.e. biggest count, and most recent at front.
|
||||
if (a.n > b.n) {
|
||||
return -1
|
||||
} else if (a.n < b.n) {
|
||||
return 1
|
||||
} else {
|
||||
if (a.ts > b.ts) {
|
||||
return -1
|
||||
} else if (a.ts < b.ts) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
9
services/contacts/app/js/mongojs.js
Normal file
9
services/contacts/app/js/mongojs.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Sanity-check the conversion and remove this comment.
|
||||
const Settings = require('settings-sharelatex')
|
||||
const mongojs = require('mongojs')
|
||||
const db = mongojs(Settings.mongo.url, ['contacts'])
|
||||
module.exports = {
|
||||
db,
|
||||
ObjectId: mongojs.ObjectId
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
contacts
|
||||
--public-repo=False
|
||||
--language=coffeescript
|
||||
--env-add=
|
||||
--node-version=10.19.0
|
||||
--acceptance-creds=None
|
||||
--dependencies=mongo
|
||||
--docker-repos=gcr.io/overleaf-ops
|
||||
--env-pass-through=
|
||||
--script-version=1.3.5
|
||||
--dependencies=mongo
|
||||
--language=es
|
||||
--docker-repos=gcr.io/overleaf-ops
|
||||
--acceptance-creds=None
|
||||
--env-pass-through=
|
||||
--env-add=
|
||||
--public-repo=False
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
http = require('http')
|
||||
http.globalAgent.maxSockets = 300
|
||||
|
||||
module.exports =
|
||||
internal:
|
||||
contacts:
|
||||
port: 3036
|
||||
host: process.env["LISTEN_ADDRESS"] or "localhost"
|
||||
|
||||
mongo:
|
||||
url: process.env['MONGO_CONNECTION_STRING'] or "mongodb://#{process.env["MONGO_HOST"] or "localhost"}/sharelatex"
|
17
services/contacts/config/settings.defaults.js
Normal file
17
services/contacts/config/settings.defaults.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
const http = require('http')
|
||||
http.globalAgent.maxSockets = 300
|
||||
|
||||
module.exports = {
|
||||
internal: {
|
||||
contacts: {
|
||||
port: 3036,
|
||||
host: process.env.LISTEN_ADDRESS || 'localhost'
|
||||
}
|
||||
},
|
||||
|
||||
mongo: {
|
||||
url:
|
||||
process.env.MONGO_CONNECTION_STRING ||
|
||||
`mongodb://${process.env.MONGO_HOST || 'localhost'}/sharelatex`
|
||||
}
|
||||
}
|
|
@ -10,10 +10,9 @@
|
|||
},
|
||||
|
||||
"watch": [
|
||||
"app/coffee/",
|
||||
"app.coffee",
|
||||
"app/js/",
|
||||
"app.js",
|
||||
"config/"
|
||||
],
|
||||
"ext": "coffee"
|
||||
|
||||
"ext": "js"
|
||||
}
|
||||
|
|
3648
services/contacts/package-lock.json
generated
3648
services/contacts/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -8,34 +8,49 @@
|
|||
"url": "https://github.com/sharelatex/contacts-sharelatex.git"
|
||||
},
|
||||
"scripts": {
|
||||
"compile:app": "([ -e app/coffee ] && coffee -m $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee -m $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')",
|
||||
"start": "npm run compile:app && node $NODE_APP_OPTIONS app.js",
|
||||
"test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js",
|
||||
"test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP",
|
||||
"test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js",
|
||||
"test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP",
|
||||
"compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee",
|
||||
"compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee",
|
||||
"compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests && npm run compile:smoke_tests",
|
||||
"start": "node $NODE_APP_OPTIONS app.js",
|
||||
"test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js",
|
||||
"test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP",
|
||||
"test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js",
|
||||
"test:unit": "npm run test:unit:_run -- --grep=$MOCHA_GREP",
|
||||
"nodemon": "nodemon --config nodemon.json",
|
||||
"compile:smoke_tests": "[ ! -e test/smoke/coffee ] && echo 'No smoke tests to compile' || coffee -o test/smoke/js -c test/smoke/coffee"
|
||||
"lint": "node_modules/.bin/eslint .",
|
||||
"format": "node_modules/.bin/prettier-eslint '**/*.js' --list-different",
|
||||
"format:fix": "node_modules/.bin/prettier-eslint '**/*.js' --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "~0.8.0",
|
||||
"body-parser": "~1.0.2",
|
||||
"coffee-script": "^1.7.1",
|
||||
"express": "~4.1.1",
|
||||
"express": "~4.5.0",
|
||||
"logger-sharelatex": "^1.7.0",
|
||||
"metrics-sharelatex": "^2.2.0",
|
||||
"mongojs": "2.4.0",
|
||||
"request": "~2.34.0",
|
||||
"request": "~2.47.0",
|
||||
"settings-sharelatex": "^1.1.0",
|
||||
"underscore": "~1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.3",
|
||||
"bunyan": "~0.22.3",
|
||||
"chai": "~1.9.1",
|
||||
"eslint": "^6.6.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-config-standard": "^14.1.0",
|
||||
"eslint-config-standard-jsx": "^8.1.0",
|
||||
"eslint-config-standard-react": "^9.2.0",
|
||||
"eslint-plugin-chai-expect": "^2.1.0",
|
||||
"eslint-plugin-chai-friendly": "^0.5.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-mocha": "^6.2.2",
|
||||
"eslint-plugin-node": "^11.0.0",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.18.3",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"mocha": "^4.0.1",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier-eslint-cli": "^5.0.0",
|
||||
"sandboxed-module": "~0.3.0",
|
||||
"sinon": "~1.5.2",
|
||||
"timekeeper": "0.0.5"
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
app = require('../../../app')
|
||||
require("logger-sharelatex").logger.level("error")
|
||||
|
||||
module.exports =
|
||||
running: false
|
||||
initing: false
|
||||
callbacks: []
|
||||
ensureRunning: (callback = (error) ->) ->
|
||||
if @running
|
||||
return callback()
|
||||
else if @initing
|
||||
@callbacks.push callback
|
||||
else
|
||||
@initing = true
|
||||
@callbacks.push callback
|
||||
app.listen 3036, "localhost", (error) =>
|
||||
throw error if error?
|
||||
@running = true
|
||||
for callback in @callbacks
|
||||
callback()
|
|
@ -1,71 +0,0 @@
|
|||
sinon = require "sinon"
|
||||
chai = require("chai")
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
ObjectId = require("mongojs").ObjectId
|
||||
request = require "request"
|
||||
async = require "async"
|
||||
ContactsApp = require "./ContactsApp"
|
||||
HOST = "http://localhost:3036"
|
||||
|
||||
describe "Getting Contacts", ->
|
||||
describe "with no contacts", ->
|
||||
beforeEach (done)->
|
||||
@user_id = ObjectId().toString()
|
||||
ContactsApp.ensureRunning done
|
||||
|
||||
it "should return an empty array", (done) ->
|
||||
request {
|
||||
method: "GET"
|
||||
url: "#{HOST}/user/#{@user_id}/contacts"
|
||||
json: true
|
||||
}, (error, response, body) ->
|
||||
response.statusCode.should.equal 200
|
||||
body.contact_ids.should.deep.equal []
|
||||
done()
|
||||
|
||||
describe "with contacts", ->
|
||||
beforeEach (done) ->
|
||||
@user_id = ObjectId().toString()
|
||||
@contact_id_1 = ObjectId().toString()
|
||||
@contact_id_2 = ObjectId().toString()
|
||||
@contact_id_3 = ObjectId().toString()
|
||||
|
||||
touchContact = (user_id, contact_id, cb) ->
|
||||
request({
|
||||
method: "POST"
|
||||
url: "#{HOST}/user/#{user_id}/contacts"
|
||||
json: {
|
||||
contact_id: contact_id
|
||||
}
|
||||
}, cb)
|
||||
|
||||
async.series [
|
||||
# 2 is preferred since touched twice, then 3 since most recent, then 1
|
||||
(cb) => ContactsApp.ensureRunning cb
|
||||
(cb) => touchContact @user_id, @contact_id_1, cb
|
||||
(cb) => touchContact @user_id, @contact_id_2, cb
|
||||
(cb) => touchContact @user_id, @contact_id_2, cb
|
||||
(cb) => touchContact @user_id, @contact_id_3, cb
|
||||
], done
|
||||
|
||||
it "should return a sorted list of contacts", (done) ->
|
||||
request {
|
||||
method: "GET"
|
||||
url: "#{HOST}/user/#{@user_id}/contacts"
|
||||
json: true
|
||||
}, (error, response, body) =>
|
||||
response.statusCode.should.equal 200
|
||||
body.contact_ids.should.deep.equal [@contact_id_2, @contact_id_3, @contact_id_1]
|
||||
done()
|
||||
|
||||
it "should respect a limit and only return top X contacts", ->
|
||||
request {
|
||||
method: "GET"
|
||||
url: "#{HOST}/user/#{@user_id}/contacts?limit=2"
|
||||
json: true
|
||||
}, (error, response, body) =>
|
||||
response.statusCode.should.equal 200
|
||||
body.contact_ids.should.deep.equal [@contact_id_2, @contact_id_3]
|
||||
done()
|
||||
|
47
services/contacts/test/acceptance/js/ContactsApp.js
Normal file
47
services/contacts/test/acceptance/js/ContactsApp.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* eslint-disable
|
||||
handle-callback-err,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const app = require('../../../app')
|
||||
require('logger-sharelatex').logger.level('error')
|
||||
|
||||
module.exports = {
|
||||
running: false,
|
||||
initing: false,
|
||||
callbacks: [],
|
||||
ensureRunning(callback) {
|
||||
if (callback == null) {
|
||||
callback = function(error) {}
|
||||
}
|
||||
if (this.running) {
|
||||
return callback()
|
||||
} else if (this.initing) {
|
||||
return this.callbacks.push(callback)
|
||||
} else {
|
||||
this.initing = true
|
||||
this.callbacks.push(callback)
|
||||
return app.listen(3036, 'localhost', error => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
this.running = true
|
||||
return (() => {
|
||||
const result = []
|
||||
for (callback of Array.from(this.callbacks)) {
|
||||
result.push(callback())
|
||||
}
|
||||
return result
|
||||
})()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
116
services/contacts/test/acceptance/js/GettingContactsTests.js
Normal file
116
services/contacts/test/acceptance/js/GettingContactsTests.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
/* eslint-disable
|
||||
camelcase,
|
||||
handle-callback-err,
|
||||
no-undef,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
chai.should()
|
||||
const { expect } = chai
|
||||
const { ObjectId } = require('mongojs')
|
||||
const request = require('request')
|
||||
const async = require('async')
|
||||
const ContactsApp = require('./ContactsApp')
|
||||
const HOST = 'http://localhost:3036'
|
||||
|
||||
describe('Getting Contacts', function() {
|
||||
describe('with no contacts', function() {
|
||||
beforeEach(function(done) {
|
||||
this.user_id = ObjectId().toString()
|
||||
return ContactsApp.ensureRunning(done)
|
||||
})
|
||||
|
||||
return it('should return an empty array', function(done) {
|
||||
return request(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `${HOST}/user/${this.user_id}/contacts`,
|
||||
json: true
|
||||
},
|
||||
(error, response, body) => {
|
||||
response.statusCode.should.equal(200)
|
||||
body.contact_ids.should.deep.equal([])
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('with contacts', function() {
|
||||
beforeEach(function(done) {
|
||||
this.user_id = ObjectId().toString()
|
||||
this.contact_id_1 = ObjectId().toString()
|
||||
this.contact_id_2 = ObjectId().toString()
|
||||
this.contact_id_3 = ObjectId().toString()
|
||||
|
||||
const touchContact = (user_id, contact_id, cb) =>
|
||||
request(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `${HOST}/user/${user_id}/contacts`,
|
||||
json: {
|
||||
contact_id
|
||||
}
|
||||
},
|
||||
cb
|
||||
)
|
||||
|
||||
return async.series(
|
||||
[
|
||||
// 2 is preferred since touched twice, then 3 since most recent, then 1
|
||||
cb => ContactsApp.ensureRunning(cb),
|
||||
cb => touchContact(this.user_id, this.contact_id_1, cb),
|
||||
cb => touchContact(this.user_id, this.contact_id_2, cb),
|
||||
cb => touchContact(this.user_id, this.contact_id_2, cb),
|
||||
cb => touchContact(this.user_id, this.contact_id_3, cb)
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should return a sorted list of contacts', function(done) {
|
||||
return request(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `${HOST}/user/${this.user_id}/contacts`,
|
||||
json: true
|
||||
},
|
||||
(error, response, body) => {
|
||||
response.statusCode.should.equal(200)
|
||||
body.contact_ids.should.deep.equal([
|
||||
this.contact_id_2,
|
||||
this.contact_id_3,
|
||||
this.contact_id_1
|
||||
])
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return it('should respect a limit and only return top X contacts', function() {
|
||||
return request(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `${HOST}/user/${this.user_id}/contacts?limit=2`,
|
||||
json: true
|
||||
},
|
||||
(error, response, body) => {
|
||||
response.statusCode.should.equal(200)
|
||||
body.contact_ids.should.deep.equal([
|
||||
this.contact_id_2,
|
||||
this.contact_id_3
|
||||
])
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,86 +0,0 @@
|
|||
sinon = require('sinon')
|
||||
chai = require('chai')
|
||||
should = chai.should()
|
||||
expect = chai.expect
|
||||
modulePath = "../../../app/js/ContactManager.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
ObjectId = require("mongojs").ObjectId
|
||||
tk = require("timekeeper")
|
||||
|
||||
describe "ContactManager", ->
|
||||
beforeEach ->
|
||||
tk.freeze(Date.now())
|
||||
@ContactManager = SandboxedModule.require modulePath, requires:
|
||||
"./mongojs": {
|
||||
db: @db = contacts: {}
|
||||
ObjectId: ObjectId
|
||||
},
|
||||
'logger-sharelatex': {log: sinon.stub()},
|
||||
'metrics-sharelatex': {timeAsyncMethod: sinon.stub()}
|
||||
@user_id = ObjectId().toString()
|
||||
@contact_id = ObjectId().toString()
|
||||
@callback = sinon.stub()
|
||||
|
||||
afterEach ->
|
||||
tk.reset()
|
||||
|
||||
describe "touchContact", ->
|
||||
beforeEach ->
|
||||
@db.contacts.update = sinon.stub().callsArg(3)
|
||||
|
||||
describe "with a valid user_id", ->
|
||||
beforeEach ->
|
||||
@ContactManager.touchContact @user_id, @contact_id = "mock_contact", @callback
|
||||
|
||||
it "should increment the contact count and timestamp", ->
|
||||
@db.contacts.update
|
||||
.calledWith({
|
||||
user_id: sinon.match((o) => o.toString() == @user_id.toString())
|
||||
}, {
|
||||
$inc:
|
||||
"contacts.mock_contact.n": 1
|
||||
$set:
|
||||
"contacts.mock_contact.ts": new Date()
|
||||
}, {
|
||||
upsert: true
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "with an invalid user id", ->
|
||||
beforeEach ->
|
||||
@ContactManager.touchContact "not-valid-object-id", @contact_id, @callback
|
||||
|
||||
it "should call the callback with an error", ->
|
||||
@callback.calledWith(new Error()).should.equal true
|
||||
|
||||
describe "getContacts", ->
|
||||
beforeEach ->
|
||||
@user = {
|
||||
contacts: ["mock", "contacts"]
|
||||
}
|
||||
@db.contacts.findOne = sinon.stub().callsArgWith(1, null, @user)
|
||||
|
||||
describe "with a valid user_id", ->
|
||||
beforeEach ->
|
||||
@ContactManager.getContacts @user_id, @callback
|
||||
|
||||
it "should find the user's contacts", ->
|
||||
@db.contacts.findOne
|
||||
.calledWith({
|
||||
user_id: sinon.match((o) => o.toString() == @user_id.toString())
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback with the contacts", ->
|
||||
@callback.calledWith(null, @user.contacts).should.equal true
|
||||
|
||||
describe "with an invalid user id", ->
|
||||
beforeEach ->
|
||||
@ContactManager.getContacts "not-valid-object-id", @callback
|
||||
|
||||
it "should call the callback with an error", ->
|
||||
@callback.calledWith(new Error()).should.equal true
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
sinon = require('sinon')
|
||||
chai = require('chai')
|
||||
should = chai.should()
|
||||
expect = chai.expect
|
||||
modulePath = "../../../app/js/HttpController.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe "HttpController", ->
|
||||
beforeEach ->
|
||||
@HttpController = SandboxedModule.require modulePath, requires:
|
||||
"./ContactManager": @ContactManager = {}
|
||||
"logger-sharelatex": @logger = { log: sinon.stub() }
|
||||
@user_id = "mock-user-id"
|
||||
@contact_id = "mock-contact-id"
|
||||
|
||||
@req = {}
|
||||
@res = {}
|
||||
@res.status = sinon.stub().returns @res
|
||||
@res.end = sinon.stub()
|
||||
@res.send = sinon.stub()
|
||||
@next = sinon.stub()
|
||||
|
||||
describe "addContact", ->
|
||||
beforeEach ->
|
||||
@req.params =
|
||||
user_id: @user_id
|
||||
@ContactManager.touchContact = sinon.stub().callsArg(2)
|
||||
|
||||
describe "with a valid user_id and contact_id", ->
|
||||
beforeEach ->
|
||||
@req.body =
|
||||
contact_id: @contact_id
|
||||
@HttpController.addContact @req, @res, @next
|
||||
|
||||
it "should update the contact in the user's contact list", ->
|
||||
@ContactManager.touchContact
|
||||
.calledWith(@user_id, @contact_id)
|
||||
.should.equal true
|
||||
|
||||
it "should update the user in the contact's contact list", ->
|
||||
@ContactManager.touchContact
|
||||
.calledWith(@contact_id, @user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should send back a 204 status", ->
|
||||
@res.status.calledWith(204).should.equal true
|
||||
@res.end.called.should.equal true
|
||||
|
||||
describe "with an invalid contact id", ->
|
||||
beforeEach ->
|
||||
@req.body =
|
||||
contact_id: ""
|
||||
@HttpController.addContact @req, @res, @next
|
||||
|
||||
it "should return 400, Bad Request", ->
|
||||
@res.status.calledWith(400).should.equal true
|
||||
@res.send.calledWith("contact_id should be a non-blank string").should.equal true
|
||||
|
||||
describe "getContacts", ->
|
||||
beforeEach ->
|
||||
@req.params =
|
||||
user_id: @user_id
|
||||
now = Date.now()
|
||||
@contacts = {
|
||||
"user-id-1": { n: 2, ts: new Date(now) }
|
||||
"user-id-2": { n: 4, ts: new Date(now) }
|
||||
"user-id-3": { n: 2, ts: new Date(now - 1000) }
|
||||
}
|
||||
@ContactManager.getContacts = sinon.stub().callsArgWith(1, null, @contacts)
|
||||
|
||||
describe "normally", ->
|
||||
beforeEach ->
|
||||
@HttpController.getContacts @req, @res, @next
|
||||
|
||||
it "should look up the contacts in mongo", ->
|
||||
@ContactManager.getContacts
|
||||
.calledWith(@user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should return a sorted list of contacts by count and timestamp", ->
|
||||
@res.send
|
||||
.calledWith({
|
||||
contact_ids: [
|
||||
"user-id-2"
|
||||
"user-id-1"
|
||||
"user-id-3"
|
||||
]
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
describe "with more contacts than the limit", ->
|
||||
beforeEach ->
|
||||
@req.query =
|
||||
limit: 2
|
||||
@HttpController.getContacts @req, @res, @next
|
||||
|
||||
it "should return the most commonly used contacts up to the limit", ->
|
||||
@res.send
|
||||
.calledWith({
|
||||
contact_ids: [
|
||||
"user-id-2"
|
||||
"user-id-1"
|
||||
]
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
describe "without a contact list", ->
|
||||
beforeEach ->
|
||||
@ContactManager.getContacts = sinon.stub().callsArgWith(1, null, null)
|
||||
@HttpController.getContacts @req, @res, @next
|
||||
|
||||
it "should return an empty list", ->
|
||||
@res.send
|
||||
.calledWith({
|
||||
contact_ids: []
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
describe "with a holding account", ->
|
||||
it "should not return holding accounts"
|
143
services/contacts/test/unit/js/ContactsManagerTests.js
Normal file
143
services/contacts/test/unit/js/ContactsManagerTests.js
Normal file
|
@ -0,0 +1,143 @@
|
|||
/* eslint-disable
|
||||
no-return-assign,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const { expect } = chai
|
||||
const modulePath = '../../../app/js/ContactManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { ObjectId } = require('mongojs')
|
||||
const tk = require('timekeeper')
|
||||
|
||||
describe('ContactManager', function() {
|
||||
beforeEach(function() {
|
||||
tk.freeze(Date.now())
|
||||
this.ContactManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./mongojs': {
|
||||
db: (this.db = { contacts: {} }),
|
||||
ObjectId
|
||||
},
|
||||
'logger-sharelatex': { log: sinon.stub() },
|
||||
'metrics-sharelatex': { timeAsyncMethod: sinon.stub() }
|
||||
}
|
||||
})
|
||||
this.user_id = ObjectId().toString()
|
||||
this.contact_id = ObjectId().toString()
|
||||
return (this.callback = sinon.stub())
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
return tk.reset()
|
||||
})
|
||||
|
||||
describe('touchContact', function() {
|
||||
beforeEach(function() {
|
||||
return (this.db.contacts.update = sinon.stub().callsArg(3))
|
||||
})
|
||||
|
||||
describe('with a valid user_id', function() {
|
||||
beforeEach(function() {
|
||||
return this.ContactManager.touchContact(
|
||||
this.user_id,
|
||||
(this.contact_id = 'mock_contact'),
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it('should increment the contact count and timestamp', function() {
|
||||
return this.db.contacts.update
|
||||
.calledWith(
|
||||
{
|
||||
user_id: sinon.match(
|
||||
o => o.toString() === this.user_id.toString()
|
||||
)
|
||||
},
|
||||
{
|
||||
$inc: {
|
||||
'contacts.mock_contact.n': 1
|
||||
},
|
||||
$set: {
|
||||
'contacts.mock_contact.ts': new Date()
|
||||
}
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
}
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it('should call the callback', function() {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('with an invalid user id', function() {
|
||||
beforeEach(function() {
|
||||
return this.ContactManager.touchContact(
|
||||
'not-valid-object-id',
|
||||
this.contact_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it('should call the callback with an error', function() {
|
||||
return this.callback.calledWith(new Error()).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return describe('getContacts', function() {
|
||||
beforeEach(function() {
|
||||
this.user = {
|
||||
contacts: ['mock', 'contacts']
|
||||
}
|
||||
return (this.db.contacts.findOne = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.user))
|
||||
})
|
||||
|
||||
describe('with a valid user_id', function() {
|
||||
beforeEach(function() {
|
||||
return this.ContactManager.getContacts(this.user_id, this.callback)
|
||||
})
|
||||
|
||||
it("should find the user's contacts", function() {
|
||||
return this.db.contacts.findOne
|
||||
.calledWith({
|
||||
user_id: sinon.match(o => o.toString() === this.user_id.toString())
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it('should call the callback with the contacts', function() {
|
||||
return this.callback
|
||||
.calledWith(null, this.user.contacts)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('with an invalid user id', function() {
|
||||
beforeEach(function() {
|
||||
return this.ContactManager.getContacts(
|
||||
'not-valid-object-id',
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it('should call the callback with an error', function() {
|
||||
return this.callback.calledWith(new Error()).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
150
services/contacts/test/unit/js/HttpControllerTests.js
Normal file
150
services/contacts/test/unit/js/HttpControllerTests.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
/* eslint-disable
|
||||
mocha/no-pending-tests,
|
||||
no-return-assign,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const { expect } = chai
|
||||
const modulePath = '../../../app/js/HttpController.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe('HttpController', function() {
|
||||
beforeEach(function() {
|
||||
this.HttpController = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./ContactManager': (this.ContactManager = {}),
|
||||
'logger-sharelatex': (this.logger = { log: sinon.stub() })
|
||||
}
|
||||
})
|
||||
this.user_id = 'mock-user-id'
|
||||
this.contact_id = 'mock-contact-id'
|
||||
|
||||
this.req = {}
|
||||
this.res = {}
|
||||
this.res.status = sinon.stub().returns(this.res)
|
||||
this.res.end = sinon.stub()
|
||||
this.res.send = sinon.stub()
|
||||
return (this.next = sinon.stub())
|
||||
})
|
||||
|
||||
describe('addContact', function() {
|
||||
beforeEach(function() {
|
||||
this.req.params = { user_id: this.user_id }
|
||||
return (this.ContactManager.touchContact = sinon.stub().callsArg(2))
|
||||
})
|
||||
|
||||
describe('with a valid user_id and contact_id', function() {
|
||||
beforeEach(function() {
|
||||
this.req.body = { contact_id: this.contact_id }
|
||||
return this.HttpController.addContact(this.req, this.res, this.next)
|
||||
})
|
||||
|
||||
it("should update the contact in the user's contact list", function() {
|
||||
return this.ContactManager.touchContact
|
||||
.calledWith(this.user_id, this.contact_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should update the user in the contact's contact list", function() {
|
||||
return this.ContactManager.touchContact
|
||||
.calledWith(this.contact_id, this.user_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it('should send back a 204 status', function() {
|
||||
this.res.status.calledWith(204).should.equal(true)
|
||||
return this.res.end.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('with an invalid contact id', function() {
|
||||
beforeEach(function() {
|
||||
this.req.body = { contact_id: '' }
|
||||
return this.HttpController.addContact(this.req, this.res, this.next)
|
||||
})
|
||||
|
||||
return it('should return 400, Bad Request', function() {
|
||||
this.res.status.calledWith(400).should.equal(true)
|
||||
return this.res.send
|
||||
.calledWith('contact_id should be a non-blank string')
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return describe('getContacts', function() {
|
||||
beforeEach(function() {
|
||||
this.req.params = { user_id: this.user_id }
|
||||
const now = Date.now()
|
||||
this.contacts = {
|
||||
'user-id-1': { n: 2, ts: new Date(now) },
|
||||
'user-id-2': { n: 4, ts: new Date(now) },
|
||||
'user-id-3': { n: 2, ts: new Date(now - 1000) }
|
||||
}
|
||||
return (this.ContactManager.getContacts = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.contacts))
|
||||
})
|
||||
|
||||
describe('normally', function() {
|
||||
beforeEach(function() {
|
||||
return this.HttpController.getContacts(this.req, this.res, this.next)
|
||||
})
|
||||
|
||||
it('should look up the contacts in mongo', function() {
|
||||
return this.ContactManager.getContacts
|
||||
.calledWith(this.user_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it('should return a sorted list of contacts by count and timestamp', function() {
|
||||
return this.res.send
|
||||
.calledWith({
|
||||
contact_ids: ['user-id-2', 'user-id-1', 'user-id-3']
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with more contacts than the limit', function() {
|
||||
beforeEach(function() {
|
||||
this.req.query = { limit: 2 }
|
||||
return this.HttpController.getContacts(this.req, this.res, this.next)
|
||||
})
|
||||
|
||||
return it('should return the most commonly used contacts up to the limit', function() {
|
||||
return this.res.send
|
||||
.calledWith({
|
||||
contact_ids: ['user-id-2', 'user-id-1']
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('without a contact list', function() {
|
||||
beforeEach(function() {
|
||||
this.ContactManager.getContacts = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null)
|
||||
return this.HttpController.getContacts(this.req, this.res, this.next)
|
||||
})
|
||||
|
||||
return it('should return an empty list', function() {
|
||||
return this.res.send
|
||||
.calledWith({
|
||||
contact_ids: []
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue