ConnectedUsersManagerTests.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. /* eslint-disable
  2. no-return-assign,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  11. */
  12. const SandboxedModule = require('sandboxed-module')
  13. const assert = require('assert')
  14. const path = require('path')
  15. const sinon = require('sinon')
  16. const modulePath = path.join(__dirname, '../../../app/js/ConnectedUsersManager')
  17. const { expect } = require('chai')
  18. const tk = require('timekeeper')
  19. describe('ConnectedUsersManager', function () {
  20. beforeEach(function () {
  21. this.settings = {
  22. redis: {
  23. realtime: {
  24. key_schema: {
  25. clientsInProject({ project_id: projectId }) {
  26. return `clients_in_project:${projectId}`
  27. },
  28. connectedUser({ project_id: projectId, client_id: clientId }) {
  29. return `connected_user:${projectId}:${clientId}`
  30. },
  31. },
  32. },
  33. },
  34. }
  35. this.rClient = {
  36. auth() {},
  37. setex: sinon.stub(),
  38. sadd: sinon.stub(),
  39. get: sinon.stub(),
  40. srem: sinon.stub(),
  41. del: sinon.stub(),
  42. smembers: sinon.stub(),
  43. expire: sinon.stub(),
  44. hset: sinon.stub(),
  45. hgetall: sinon.stub(),
  46. exec: sinon.stub(),
  47. multi: () => {
  48. return this.rClient
  49. },
  50. }
  51. tk.freeze(new Date())
  52. this.ConnectedUsersManager = SandboxedModule.require(modulePath, {
  53. requires: {
  54. '@overleaf/settings': this.settings,
  55. '@overleaf/redis-wrapper': {
  56. createClient: () => {
  57. return this.rClient
  58. },
  59. },
  60. },
  61. })
  62. this.client_id = '32132132'
  63. this.project_id = 'dskjh2u21321'
  64. this.user = {
  65. _id: 'user-id-123',
  66. first_name: 'Joe',
  67. last_name: 'Bloggs',
  68. email: 'joe@example.com',
  69. }
  70. return (this.cursorData = {
  71. row: 12,
  72. column: 9,
  73. doc_id: '53c3b8c85fee64000023dc6e',
  74. })
  75. })
  76. afterEach(function () {
  77. return tk.reset()
  78. })
  79. describe('updateUserPosition', function () {
  80. beforeEach(function () {
  81. return this.rClient.exec.callsArgWith(0)
  82. })
  83. it('should set a key with the date and give it a ttl', function (done) {
  84. return this.ConnectedUsersManager.updateUserPosition(
  85. this.project_id,
  86. this.client_id,
  87. this.user,
  88. null,
  89. err => {
  90. if (err) return done(err)
  91. this.rClient.hset
  92. .calledWith(
  93. `connected_user:${this.project_id}:${this.client_id}`,
  94. 'last_updated_at',
  95. Date.now()
  96. )
  97. .should.equal(true)
  98. return done()
  99. }
  100. )
  101. })
  102. it('should set a key with the user_id', function (done) {
  103. return this.ConnectedUsersManager.updateUserPosition(
  104. this.project_id,
  105. this.client_id,
  106. this.user,
  107. null,
  108. err => {
  109. if (err) return done(err)
  110. this.rClient.hset
  111. .calledWith(
  112. `connected_user:${this.project_id}:${this.client_id}`,
  113. 'user_id',
  114. this.user._id
  115. )
  116. .should.equal(true)
  117. return done()
  118. }
  119. )
  120. })
  121. it('should set a key with the first_name', function (done) {
  122. return this.ConnectedUsersManager.updateUserPosition(
  123. this.project_id,
  124. this.client_id,
  125. this.user,
  126. null,
  127. err => {
  128. if (err) return done(err)
  129. this.rClient.hset
  130. .calledWith(
  131. `connected_user:${this.project_id}:${this.client_id}`,
  132. 'first_name',
  133. this.user.first_name
  134. )
  135. .should.equal(true)
  136. return done()
  137. }
  138. )
  139. })
  140. it('should set a key with the last_name', function (done) {
  141. return this.ConnectedUsersManager.updateUserPosition(
  142. this.project_id,
  143. this.client_id,
  144. this.user,
  145. null,
  146. err => {
  147. if (err) return done(err)
  148. this.rClient.hset
  149. .calledWith(
  150. `connected_user:${this.project_id}:${this.client_id}`,
  151. 'last_name',
  152. this.user.last_name
  153. )
  154. .should.equal(true)
  155. return done()
  156. }
  157. )
  158. })
  159. it('should set a key with the email', function (done) {
  160. return this.ConnectedUsersManager.updateUserPosition(
  161. this.project_id,
  162. this.client_id,
  163. this.user,
  164. null,
  165. err => {
  166. if (err) return done(err)
  167. this.rClient.hset
  168. .calledWith(
  169. `connected_user:${this.project_id}:${this.client_id}`,
  170. 'email',
  171. this.user.email
  172. )
  173. .should.equal(true)
  174. return done()
  175. }
  176. )
  177. })
  178. it('should push the client_id on to the project list', function (done) {
  179. return this.ConnectedUsersManager.updateUserPosition(
  180. this.project_id,
  181. this.client_id,
  182. this.user,
  183. null,
  184. err => {
  185. if (err) return done(err)
  186. this.rClient.sadd
  187. .calledWith(`clients_in_project:${this.project_id}`, this.client_id)
  188. .should.equal(true)
  189. return done()
  190. }
  191. )
  192. })
  193. it('should add a ttl to the project set so it stays clean', function (done) {
  194. return this.ConnectedUsersManager.updateUserPosition(
  195. this.project_id,
  196. this.client_id,
  197. this.user,
  198. null,
  199. err => {
  200. if (err) return done(err)
  201. this.rClient.expire
  202. .calledWith(
  203. `clients_in_project:${this.project_id}`,
  204. 24 * 4 * 60 * 60
  205. )
  206. .should.equal(true)
  207. return done()
  208. }
  209. )
  210. })
  211. it('should add a ttl to the connected user so it stays clean', function (done) {
  212. return this.ConnectedUsersManager.updateUserPosition(
  213. this.project_id,
  214. this.client_id,
  215. this.user,
  216. null,
  217. err => {
  218. if (err) return done(err)
  219. this.rClient.expire
  220. .calledWith(
  221. `connected_user:${this.project_id}:${this.client_id}`,
  222. 60 * 15
  223. )
  224. .should.equal(true)
  225. return done()
  226. }
  227. )
  228. })
  229. return it('should set the cursor position when provided', function (done) {
  230. return this.ConnectedUsersManager.updateUserPosition(
  231. this.project_id,
  232. this.client_id,
  233. this.user,
  234. this.cursorData,
  235. err => {
  236. if (err) return done(err)
  237. this.rClient.hset
  238. .calledWith(
  239. `connected_user:${this.project_id}:${this.client_id}`,
  240. 'cursorData',
  241. JSON.stringify(this.cursorData)
  242. )
  243. .should.equal(true)
  244. return done()
  245. }
  246. )
  247. })
  248. })
  249. describe('markUserAsDisconnected', function () {
  250. beforeEach(function () {
  251. return this.rClient.exec.callsArgWith(0)
  252. })
  253. it('should remove the user from the set', function (done) {
  254. return this.ConnectedUsersManager.markUserAsDisconnected(
  255. this.project_id,
  256. this.client_id,
  257. err => {
  258. if (err) return done(err)
  259. this.rClient.srem
  260. .calledWith(`clients_in_project:${this.project_id}`, this.client_id)
  261. .should.equal(true)
  262. return done()
  263. }
  264. )
  265. })
  266. it('should delete the connected_user string', function (done) {
  267. return this.ConnectedUsersManager.markUserAsDisconnected(
  268. this.project_id,
  269. this.client_id,
  270. err => {
  271. if (err) return done(err)
  272. this.rClient.del
  273. .calledWith(`connected_user:${this.project_id}:${this.client_id}`)
  274. .should.equal(true)
  275. return done()
  276. }
  277. )
  278. })
  279. return it('should add a ttl to the connected user set so it stays clean', function (done) {
  280. return this.ConnectedUsersManager.markUserAsDisconnected(
  281. this.project_id,
  282. this.client_id,
  283. err => {
  284. if (err) return done(err)
  285. this.rClient.expire
  286. .calledWith(
  287. `clients_in_project:${this.project_id}`,
  288. 24 * 4 * 60 * 60
  289. )
  290. .should.equal(true)
  291. return done()
  292. }
  293. )
  294. })
  295. })
  296. describe('_getConnectedUser', function () {
  297. it('should return a connected user if there is a user object', function (done) {
  298. const cursorData = JSON.stringify({ cursorData: { row: 1 } })
  299. this.rClient.hgetall.callsArgWith(1, null, {
  300. connected_at: new Date(),
  301. user_id: this.user._id,
  302. last_updated_at: `${Date.now()}`,
  303. cursorData,
  304. })
  305. return this.ConnectedUsersManager._getConnectedUser(
  306. this.project_id,
  307. this.client_id,
  308. (err, result) => {
  309. if (err) return done(err)
  310. result.connected.should.equal(true)
  311. result.client_id.should.equal(this.client_id)
  312. return done()
  313. }
  314. )
  315. })
  316. it('should return a not connected user if there is no object', function (done) {
  317. this.rClient.hgetall.callsArgWith(1, null, null)
  318. return this.ConnectedUsersManager._getConnectedUser(
  319. this.project_id,
  320. this.client_id,
  321. (err, result) => {
  322. if (err) return done(err)
  323. result.connected.should.equal(false)
  324. result.client_id.should.equal(this.client_id)
  325. return done()
  326. }
  327. )
  328. })
  329. return it('should return a not connected user if there is an empty object', function (done) {
  330. this.rClient.hgetall.callsArgWith(1, null, {})
  331. return this.ConnectedUsersManager._getConnectedUser(
  332. this.project_id,
  333. this.client_id,
  334. (err, result) => {
  335. if (err) return done(err)
  336. result.connected.should.equal(false)
  337. result.client_id.should.equal(this.client_id)
  338. return done()
  339. }
  340. )
  341. })
  342. })
  343. return describe('getConnectedUsers', function () {
  344. beforeEach(function () {
  345. this.users = ['1234', '5678', '9123', '8234']
  346. this.rClient.smembers.callsArgWith(1, null, this.users)
  347. this.ConnectedUsersManager._getConnectedUser = sinon.stub()
  348. this.ConnectedUsersManager._getConnectedUser
  349. .withArgs(this.project_id, this.users[0])
  350. .callsArgWith(2, null, {
  351. connected: true,
  352. client_age: 2,
  353. client_id: this.users[0],
  354. })
  355. this.ConnectedUsersManager._getConnectedUser
  356. .withArgs(this.project_id, this.users[1])
  357. .callsArgWith(2, null, {
  358. connected: false,
  359. client_age: 1,
  360. client_id: this.users[1],
  361. })
  362. this.ConnectedUsersManager._getConnectedUser
  363. .withArgs(this.project_id, this.users[2])
  364. .callsArgWith(2, null, {
  365. connected: true,
  366. client_age: 3,
  367. client_id: this.users[2],
  368. })
  369. return this.ConnectedUsersManager._getConnectedUser
  370. .withArgs(this.project_id, this.users[3])
  371. .callsArgWith(2, null, {
  372. connected: true,
  373. client_age: 11,
  374. client_id: this.users[3],
  375. })
  376. }) // connected but old
  377. return it('should only return the users in the list which are still in redis and recently updated', function (done) {
  378. return this.ConnectedUsersManager.getConnectedUsers(
  379. this.project_id,
  380. (err, users) => {
  381. if (err) return done(err)
  382. users.length.should.equal(2)
  383. users[0].should.deep.equal({
  384. client_id: this.users[0],
  385. client_age: 2,
  386. connected: true,
  387. })
  388. users[1].should.deep.equal({
  389. client_id: this.users[2],
  390. client_age: 3,
  391. connected: true,
  392. })
  393. return done()
  394. }
  395. )
  396. })
  397. })
  398. })