2020-06-23 13:30:03 -04:00
|
|
|
/* eslint-disable
|
|
|
|
no-return-assign,
|
|
|
|
no-unused-vars,
|
|
|
|
*/
|
|
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
// Fix any style issues and re-enable lint.
|
2020-06-23 13:29:59 -04:00
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
|
|
|
|
2020-06-23 13:30:16 -04:00
|
|
|
const SandboxedModule = require('sandboxed-module')
|
2024-11-08 05:21:56 -05:00
|
|
|
const assert = require('node:assert')
|
|
|
|
const path = require('node:path')
|
2020-06-23 13:30:16 -04:00
|
|
|
const sinon = require('sinon')
|
|
|
|
const modulePath = path.join(__dirname, '../../../app/js/ConnectedUsersManager')
|
|
|
|
const { expect } = require('chai')
|
|
|
|
const tk = require('timekeeper')
|
2014-11-13 07:27:46 -05:00
|
|
|
|
2020-06-23 13:30:16 -04:00
|
|
|
describe('ConnectedUsersManager', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
this.settings = {
|
|
|
|
redis: {
|
|
|
|
realtime: {
|
|
|
|
key_schema: {
|
2023-03-20 10:10:40 -04:00
|
|
|
clientsInProject({ project_id: projectId }) {
|
|
|
|
return `clients_in_project:${projectId}`
|
2020-06-23 13:30:16 -04:00
|
|
|
},
|
2023-03-20 10:10:40 -04:00
|
|
|
connectedUser({ project_id: projectId, client_id: clientId }) {
|
|
|
|
return `connected_user:${projectId}:${clientId}`
|
2021-07-13 07:04:45 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-06-23 13:30:16 -04:00
|
|
|
}
|
|
|
|
this.rClient = {
|
|
|
|
auth() {},
|
|
|
|
setex: sinon.stub(),
|
|
|
|
sadd: sinon.stub(),
|
|
|
|
get: sinon.stub(),
|
|
|
|
srem: sinon.stub(),
|
|
|
|
del: sinon.stub(),
|
|
|
|
smembers: sinon.stub(),
|
|
|
|
expire: sinon.stub(),
|
|
|
|
hset: sinon.stub(),
|
|
|
|
hgetall: sinon.stub(),
|
|
|
|
exec: sinon.stub(),
|
|
|
|
multi: () => {
|
|
|
|
return this.rClient
|
2021-07-13 07:04:45 -04:00
|
|
|
},
|
2020-06-23 13:30:16 -04:00
|
|
|
}
|
|
|
|
tk.freeze(new Date())
|
|
|
|
|
|
|
|
this.ConnectedUsersManager = SandboxedModule.require(modulePath, {
|
|
|
|
requires: {
|
2021-07-12 12:47:18 -04:00
|
|
|
'@overleaf/settings': this.settings,
|
2020-11-10 06:32:06 -05:00
|
|
|
'@overleaf/redis-wrapper': {
|
2020-06-23 13:30:16 -04:00
|
|
|
createClient: () => {
|
|
|
|
return this.rClient
|
2021-07-13 07:04:45 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-06-23 13:30:16 -04:00
|
|
|
})
|
|
|
|
this.client_id = '32132132'
|
|
|
|
this.project_id = 'dskjh2u21321'
|
|
|
|
this.user = {
|
|
|
|
_id: 'user-id-123',
|
|
|
|
first_name: 'Joe',
|
|
|
|
last_name: 'Bloggs',
|
2021-07-13 07:04:45 -04:00
|
|
|
email: 'joe@example.com',
|
2020-06-23 13:30:16 -04:00
|
|
|
}
|
|
|
|
return (this.cursorData = {
|
|
|
|
row: 12,
|
|
|
|
column: 9,
|
2021-07-13 07:04:45 -04:00
|
|
|
doc_id: '53c3b8c85fee64000023dc6e',
|
2020-06-23 13:30:16 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
afterEach(function () {
|
|
|
|
return tk.reset()
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('updateUserPosition', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
return this.rClient.exec.callsArgWith(0)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should set a key with the date and give it a ttl', function (done) {
|
|
|
|
return this.ConnectedUsersManager.updateUserPosition(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
this.user,
|
|
|
|
null,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.hset
|
|
|
|
.calledWith(
|
|
|
|
`connected_user:${this.project_id}:${this.client_id}`,
|
|
|
|
'last_updated_at',
|
|
|
|
Date.now()
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should set a key with the user_id', function (done) {
|
|
|
|
return this.ConnectedUsersManager.updateUserPosition(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
this.user,
|
|
|
|
null,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.hset
|
|
|
|
.calledWith(
|
|
|
|
`connected_user:${this.project_id}:${this.client_id}`,
|
|
|
|
'user_id',
|
|
|
|
this.user._id
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should set a key with the first_name', function (done) {
|
|
|
|
return this.ConnectedUsersManager.updateUserPosition(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
this.user,
|
|
|
|
null,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.hset
|
|
|
|
.calledWith(
|
|
|
|
`connected_user:${this.project_id}:${this.client_id}`,
|
|
|
|
'first_name',
|
|
|
|
this.user.first_name
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should set a key with the last_name', function (done) {
|
|
|
|
return this.ConnectedUsersManager.updateUserPosition(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
this.user,
|
|
|
|
null,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.hset
|
|
|
|
.calledWith(
|
|
|
|
`connected_user:${this.project_id}:${this.client_id}`,
|
|
|
|
'last_name',
|
|
|
|
this.user.last_name
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should set a key with the email', function (done) {
|
|
|
|
return this.ConnectedUsersManager.updateUserPosition(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
this.user,
|
|
|
|
null,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.hset
|
|
|
|
.calledWith(
|
|
|
|
`connected_user:${this.project_id}:${this.client_id}`,
|
|
|
|
'email',
|
|
|
|
this.user.email
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should push the client_id on to the project list', function (done) {
|
|
|
|
return this.ConnectedUsersManager.updateUserPosition(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
this.user,
|
|
|
|
null,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.sadd
|
|
|
|
.calledWith(`clients_in_project:${this.project_id}`, this.client_id)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should add a ttl to the project set so it stays clean', function (done) {
|
|
|
|
return this.ConnectedUsersManager.updateUserPosition(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
this.user,
|
|
|
|
null,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.expire
|
|
|
|
.calledWith(
|
|
|
|
`clients_in_project:${this.project_id}`,
|
|
|
|
24 * 4 * 60 * 60
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should add a ttl to the connected user so it stays clean', function (done) {
|
|
|
|
return this.ConnectedUsersManager.updateUserPosition(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
this.user,
|
|
|
|
null,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.expire
|
|
|
|
.calledWith(
|
|
|
|
`connected_user:${this.project_id}:${this.client_id}`,
|
|
|
|
60 * 15
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
return it('should set the cursor position when provided', function (done) {
|
|
|
|
return this.ConnectedUsersManager.updateUserPosition(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
this.user,
|
|
|
|
this.cursorData,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.hset
|
|
|
|
.calledWith(
|
|
|
|
`connected_user:${this.project_id}:${this.client_id}`,
|
|
|
|
'cursorData',
|
|
|
|
JSON.stringify(this.cursorData)
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('markUserAsDisconnected', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
return this.rClient.exec.callsArgWith(0)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should remove the user from the set', function (done) {
|
|
|
|
return this.ConnectedUsersManager.markUserAsDisconnected(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.srem
|
|
|
|
.calledWith(`clients_in_project:${this.project_id}`, this.client_id)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should delete the connected_user string', function (done) {
|
|
|
|
return this.ConnectedUsersManager.markUserAsDisconnected(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.del
|
|
|
|
.calledWith(`connected_user:${this.project_id}:${this.client_id}`)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
return it('should add a ttl to the connected user set so it stays clean', function (done) {
|
|
|
|
return this.ConnectedUsersManager.markUserAsDisconnected(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
2021-07-13 07:04:45 -04:00
|
|
|
err => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
this.rClient.expire
|
|
|
|
.calledWith(
|
|
|
|
`clients_in_project:${this.project_id}`,
|
|
|
|
24 * 4 * 60 * 60
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('_getConnectedUser', function () {
|
|
|
|
it('should return a connected user if there is a user object', function (done) {
|
|
|
|
const cursorData = JSON.stringify({ cursorData: { row: 1 } })
|
|
|
|
this.rClient.hgetall.callsArgWith(1, null, {
|
|
|
|
connected_at: new Date(),
|
|
|
|
user_id: this.user._id,
|
|
|
|
last_updated_at: `${Date.now()}`,
|
2021-07-13 07:04:45 -04:00
|
|
|
cursorData,
|
2020-06-23 13:30:16 -04:00
|
|
|
})
|
|
|
|
return this.ConnectedUsersManager._getConnectedUser(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
(err, result) => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
result.connected.should.equal(true)
|
|
|
|
result.client_id.should.equal(this.client_id)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should return a not connected user if there is no object', function (done) {
|
|
|
|
this.rClient.hgetall.callsArgWith(1, null, null)
|
|
|
|
return this.ConnectedUsersManager._getConnectedUser(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
(err, result) => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
result.connected.should.equal(false)
|
|
|
|
result.client_id.should.equal(this.client_id)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
return it('should return a not connected user if there is an empty object', function (done) {
|
|
|
|
this.rClient.hgetall.callsArgWith(1, null, {})
|
|
|
|
return this.ConnectedUsersManager._getConnectedUser(
|
|
|
|
this.project_id,
|
|
|
|
this.client_id,
|
|
|
|
(err, result) => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
result.connected.should.equal(false)
|
|
|
|
result.client_id.should.equal(this.client_id)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
return describe('getConnectedUsers', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
this.users = ['1234', '5678', '9123', '8234']
|
|
|
|
this.rClient.smembers.callsArgWith(1, null, this.users)
|
|
|
|
this.ConnectedUsersManager._getConnectedUser = sinon.stub()
|
|
|
|
this.ConnectedUsersManager._getConnectedUser
|
|
|
|
.withArgs(this.project_id, this.users[0])
|
|
|
|
.callsArgWith(2, null, {
|
|
|
|
connected: true,
|
|
|
|
client_age: 2,
|
2021-07-13 07:04:45 -04:00
|
|
|
client_id: this.users[0],
|
2020-06-23 13:30:16 -04:00
|
|
|
})
|
|
|
|
this.ConnectedUsersManager._getConnectedUser
|
|
|
|
.withArgs(this.project_id, this.users[1])
|
|
|
|
.callsArgWith(2, null, {
|
|
|
|
connected: false,
|
|
|
|
client_age: 1,
|
2021-07-13 07:04:45 -04:00
|
|
|
client_id: this.users[1],
|
2020-06-23 13:30:16 -04:00
|
|
|
})
|
|
|
|
this.ConnectedUsersManager._getConnectedUser
|
|
|
|
.withArgs(this.project_id, this.users[2])
|
|
|
|
.callsArgWith(2, null, {
|
|
|
|
connected: true,
|
|
|
|
client_age: 3,
|
2021-07-13 07:04:45 -04:00
|
|
|
client_id: this.users[2],
|
2020-06-23 13:30:16 -04:00
|
|
|
})
|
|
|
|
return this.ConnectedUsersManager._getConnectedUser
|
|
|
|
.withArgs(this.project_id, this.users[3])
|
|
|
|
.callsArgWith(2, null, {
|
|
|
|
connected: true,
|
|
|
|
client_age: 11,
|
2021-07-13 07:04:45 -04:00
|
|
|
client_id: this.users[3],
|
2020-06-23 13:30:16 -04:00
|
|
|
})
|
|
|
|
}) // connected but old
|
|
|
|
|
|
|
|
return it('should only return the users in the list which are still in redis and recently updated', function (done) {
|
|
|
|
return this.ConnectedUsersManager.getConnectedUsers(
|
|
|
|
this.project_id,
|
|
|
|
(err, users) => {
|
2021-10-27 05:49:18 -04:00
|
|
|
if (err) return done(err)
|
2020-06-23 13:30:16 -04:00
|
|
|
users.length.should.equal(2)
|
|
|
|
users[0].should.deep.equal({
|
|
|
|
client_id: this.users[0],
|
|
|
|
client_age: 2,
|
2021-07-13 07:04:45 -04:00
|
|
|
connected: true,
|
2020-06-23 13:30:16 -04:00
|
|
|
})
|
|
|
|
users[1].should.deep.equal({
|
|
|
|
client_id: this.users[2],
|
|
|
|
client_age: 3,
|
2021-07-13 07:04:45 -04:00
|
|
|
connected: true,
|
2020-06-23 13:30:16 -04:00
|
|
|
})
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|