ConnectedUsersManagerTests.js 11 KB

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