TpdsUpdateSenderTests.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. const { ObjectId } = require('mongodb')
  2. const SandboxedModule = require('sandboxed-module')
  3. const path = require('path')
  4. const sinon = require('sinon')
  5. const modulePath = path.join(
  6. __dirname,
  7. '../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js'
  8. )
  9. const projectId = 'project_id_here'
  10. const userId = ObjectId()
  11. const readOnlyRef = ObjectId()
  12. const collaberatorRef = ObjectId()
  13. const projectName = 'project_name_here'
  14. const thirdPartyDataStoreApiUrl = 'http://third-party-json-store.herokuapp.com'
  15. const siteUrl = 'http://www.localhost:3000'
  16. const filestoreUrl = 'filestore.sharelatex.com'
  17. const projectArchiverUrl = 'project-archiver.overleaf.com'
  18. describe('TpdsUpdateSender', function () {
  19. beforeEach(function () {
  20. this.fakeUser = {
  21. _id: '12390i',
  22. }
  23. this.requestQueuer = function (queue, meth, opts, callback) {}
  24. const memberIds = [userId, collaberatorRef, readOnlyRef]
  25. this.CollaboratorsGetter = {
  26. promises: {
  27. getInvitedMemberIds: sinon.stub().resolves(memberIds),
  28. },
  29. }
  30. this.docstoreUrl = 'docstore.sharelatex.env'
  31. this.request = sinon.stub().resolves()
  32. this.settings = {
  33. siteUrl,
  34. apis: {
  35. thirdPartyDataStore: { url: thirdPartyDataStoreApiUrl },
  36. filestore: {
  37. url: filestoreUrl,
  38. },
  39. docstore: {
  40. pubUrl: this.docstoreUrl,
  41. },
  42. },
  43. }
  44. const getUsers = sinon.stub().resolves(
  45. memberIds.slice(1).map(userId => {
  46. return { _id: userId }
  47. })
  48. )
  49. this.UserGetter = {
  50. promises: { getUsers },
  51. }
  52. this.updateSender = SandboxedModule.require(modulePath, {
  53. requires: {
  54. mongodb: { ObjectId },
  55. '@overleaf/settings': this.settings,
  56. 'request-promise-native': this.request,
  57. '../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
  58. '../User/UserGetter.js': this.UserGetter,
  59. '@overleaf/metrics': {
  60. inc() {},
  61. },
  62. },
  63. })
  64. })
  65. describe('enqueue', function () {
  66. it('should not call request if there is no tpdsworker url', async function () {
  67. await this.updateSender.promises.enqueue(null, null, null)
  68. this.request.should.not.have.been.called
  69. })
  70. it('should post the message to the tpdsworker', async function () {
  71. this.settings.apis.tpdsworker = { url: 'www.tpdsworker.env' }
  72. const group0 = 'myproject'
  73. const method0 = 'somemethod0'
  74. const job0 = 'do something'
  75. await this.updateSender.promises.enqueue(group0, method0, job0)
  76. const args = this.request.firstCall.args[0]
  77. args.json.group.should.equal(group0)
  78. args.json.job.should.equal(job0)
  79. args.json.method.should.equal(method0)
  80. args.uri.should.equal(
  81. 'www.tpdsworker.env/enqueue/web_to_tpds_http_requests'
  82. )
  83. })
  84. })
  85. describe('sending updates', function () {
  86. beforeEach(function () {
  87. this.settings.apis.tpdsworker = { url: 'www.tpdsworker.env' }
  88. })
  89. it('queues a post the file with user and file id', async function () {
  90. const fileId = '4545345'
  91. const path = '/some/path/here.jpg'
  92. await this.updateSender.promises.addFile({
  93. project_id: projectId,
  94. file_id: fileId,
  95. path,
  96. project_name: projectName,
  97. })
  98. const {
  99. group: group0,
  100. job: job0,
  101. method: method0,
  102. } = this.request.firstCall.args[0].json
  103. group0.should.equal(userId)
  104. method0.should.equal('pipeStreamFrom')
  105. job0.method.should.equal('post')
  106. job0.streamOrigin.should.equal(
  107. `${filestoreUrl}/project/${projectId}/file/${fileId}`
  108. )
  109. const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
  110. projectName
  111. )}${encodeURIComponent(path)}`
  112. job0.uri.should.equal(expectedUrl)
  113. job0.headers.sl_all_user_ids.should.equal(JSON.stringify([userId]))
  114. job0.headers.sl_project_owner_user_id.should.equal(userId)
  115. const { group: group1, job: job1 } = this.request.secondCall.args[0].json
  116. group1.should.equal(collaberatorRef)
  117. job1.headers.sl_all_user_ids.should.equal(
  118. JSON.stringify([collaberatorRef])
  119. )
  120. job1.headers.sl_project_owner_user_id.should.equal(userId)
  121. const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
  122. group2.should.equal(readOnlyRef)
  123. job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))
  124. job2.headers.sl_project_owner_user_id.should.equal(userId)
  125. this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
  126. {
  127. _id: {
  128. $in: [collaberatorRef, readOnlyRef],
  129. },
  130. 'dropbox.access_token.uid': { $ne: null },
  131. },
  132. { _id: 1 }
  133. )
  134. })
  135. it('post doc with stream origin of docstore', async function () {
  136. const docId = '4545345'
  137. const path = '/some/path/here.tex'
  138. const lines = ['line1', 'line2', 'line3']
  139. await this.updateSender.promises.addDoc({
  140. project_id: projectId,
  141. doc_id: docId,
  142. path,
  143. docLines: lines,
  144. project_name: projectName,
  145. })
  146. const {
  147. group: group0,
  148. job: job0,
  149. method: method0,
  150. } = this.request.firstCall.args[0].json
  151. group0.should.equal(userId)
  152. method0.should.equal('pipeStreamFrom')
  153. job0.method.should.equal('post')
  154. const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
  155. projectName
  156. )}${encodeURIComponent(path)}`
  157. job0.uri.should.equal(expectedUrl)
  158. job0.streamOrigin.should.equal(
  159. `${this.docstoreUrl}/project/${projectId}/doc/${docId}/raw`
  160. )
  161. job0.headers.sl_all_user_ids.should.eql(JSON.stringify([userId]))
  162. const { group: group1, job: job1 } = this.request.secondCall.args[0].json
  163. group1.should.equal(collaberatorRef)
  164. job1.headers.sl_all_user_ids.should.equal(
  165. JSON.stringify([collaberatorRef])
  166. )
  167. const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
  168. group2.should.equal(readOnlyRef)
  169. job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))
  170. this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
  171. {
  172. _id: {
  173. $in: [collaberatorRef, readOnlyRef],
  174. },
  175. 'dropbox.access_token.uid': { $ne: null },
  176. },
  177. { _id: 1 }
  178. )
  179. })
  180. it('deleting entity', async function () {
  181. const path = '/path/here/t.tex'
  182. await this.updateSender.promises.deleteEntity({
  183. project_id: projectId,
  184. path,
  185. project_name: projectName,
  186. })
  187. const {
  188. group: group0,
  189. job: job0,
  190. method: method0,
  191. } = this.request.firstCall.args[0].json
  192. group0.should.equal(userId)
  193. method0.should.equal('standardHttpRequest')
  194. job0.method.should.equal('delete')
  195. const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
  196. projectName
  197. )}${encodeURIComponent(path)}`
  198. job0.headers.sl_all_user_ids.should.eql(JSON.stringify([userId]))
  199. job0.uri.should.equal(expectedUrl)
  200. const { group: group1, job: job1 } = this.request.secondCall.args[0].json
  201. group1.should.equal(collaberatorRef)
  202. job1.headers.sl_all_user_ids.should.equal(
  203. JSON.stringify([collaberatorRef])
  204. )
  205. const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
  206. group2.should.equal(readOnlyRef)
  207. job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))
  208. this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
  209. {
  210. _id: {
  211. $in: [collaberatorRef, readOnlyRef],
  212. },
  213. 'dropbox.access_token.uid': { $ne: null },
  214. },
  215. { _id: 1 }
  216. )
  217. })
  218. it('moving entity', async function () {
  219. const startPath = 'staring/here/file.tex'
  220. const endPath = 'ending/here/file.tex'
  221. await this.updateSender.promises.moveEntity({
  222. project_id: projectId,
  223. startPath,
  224. endPath,
  225. project_name: projectName,
  226. })
  227. const {
  228. group: group0,
  229. job: job0,
  230. method: method0,
  231. } = this.request.firstCall.args[0].json
  232. group0.should.equal(userId)
  233. method0.should.equal('standardHttpRequest')
  234. job0.method.should.equal('put')
  235. job0.uri.should.equal(
  236. `${thirdPartyDataStoreApiUrl}/user/${userId}/entity`
  237. )
  238. job0.json.startPath.should.equal(`/${projectName}/${startPath}`)
  239. job0.json.endPath.should.equal(`/${projectName}/${endPath}`)
  240. job0.headers.sl_all_user_ids.should.eql(JSON.stringify([userId]))
  241. const { group: group1, job: job1 } = this.request.secondCall.args[0].json
  242. group1.should.equal(collaberatorRef)
  243. job1.headers.sl_all_user_ids.should.equal(
  244. JSON.stringify([collaberatorRef])
  245. )
  246. const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
  247. group2.should.equal(readOnlyRef)
  248. job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))
  249. this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
  250. {
  251. _id: {
  252. $in: [collaberatorRef, readOnlyRef],
  253. },
  254. 'dropbox.access_token.uid': { $ne: null },
  255. },
  256. { _id: 1 }
  257. )
  258. })
  259. it('should be able to rename a project using the move entity func', async function () {
  260. const oldProjectName = '/oldProjectName/'
  261. const newProjectName = '/newProjectName/'
  262. await this.updateSender.promises.moveEntity({
  263. project_id: projectId,
  264. project_name: oldProjectName,
  265. newProjectName,
  266. })
  267. const {
  268. group: group0,
  269. job: job0,
  270. method: method0,
  271. } = this.request.firstCall.args[0].json
  272. group0.should.equal(userId)
  273. method0.should.equal('standardHttpRequest')
  274. job0.method.should.equal('put')
  275. job0.uri.should.equal(
  276. `${thirdPartyDataStoreApiUrl}/user/${userId}/entity`
  277. )
  278. job0.json.startPath.should.equal(oldProjectName)
  279. job0.json.endPath.should.equal(newProjectName)
  280. job0.headers.sl_all_user_ids.should.eql(JSON.stringify([userId]))
  281. const { group: group1, job: job1 } = this.request.secondCall.args[0].json
  282. group1.should.equal(collaberatorRef)
  283. job1.headers.sl_all_user_ids.should.equal(
  284. JSON.stringify([collaberatorRef])
  285. )
  286. const { group: group2, job: job2 } = this.request.thirdCall.args[0].json
  287. group2.should.equal(readOnlyRef)
  288. job2.headers.sl_all_user_ids.should.equal(JSON.stringify([readOnlyRef]))
  289. this.UserGetter.promises.getUsers.should.have.been.calledOnce.and.calledWith(
  290. {
  291. _id: {
  292. $in: [collaberatorRef, readOnlyRef],
  293. },
  294. 'dropbox.access_token.uid': { $ne: null },
  295. },
  296. { _id: 1 }
  297. )
  298. })
  299. it('pollDropboxForUser', async function () {
  300. await this.updateSender.promises.pollDropboxForUser(userId)
  301. const {
  302. group: group0,
  303. job: job0,
  304. method: method0,
  305. } = this.request.firstCall.args[0].json
  306. group0.should.equal(`poll-dropbox:${userId}`)
  307. method0.should.equal('standardHttpRequest')
  308. job0.method.should.equal('post')
  309. job0.uri.should.equal(`${thirdPartyDataStoreApiUrl}/user/poll`)
  310. job0.json.user_ids[0].should.equal(userId)
  311. })
  312. })
  313. describe('deleteProject', function () {
  314. it('should not call request if there is no project archiver url', async function () {
  315. await this.updateSender.promises.deleteProject({ project_id: projectId })
  316. this.request.should.not.have.been.called
  317. })
  318. it('should make a delete request to project archiver', async function () {
  319. this.settings.apis.project_archiver = { url: projectArchiverUrl }
  320. await this.updateSender.promises.deleteProject({ project_id: projectId })
  321. const { uri, method } = this.request.firstCall.args[0]
  322. method.should.equal('delete')
  323. uri.should.equal(`${projectArchiverUrl}/project/${projectId}`)
  324. })
  325. })
  326. })