TpdsUpdateSenderTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. const { ObjectId } = require('mongodb')
  2. const SandboxedModule = require('sandboxed-module')
  3. const path = require('path')
  4. const sinon = require('sinon')
  5. const { expect } = require('chai')
  6. const modulePath = path.join(
  7. __dirname,
  8. '../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js'
  9. )
  10. const projectId = 'project_id_here'
  11. const userId = ObjectId()
  12. const readOnlyRef = ObjectId()
  13. const collaberatorRef = ObjectId()
  14. const projectName = 'project_name_here'
  15. const thirdPartyDataStoreApiUrl = 'http://third-party-json-store.herokuapp.com'
  16. const siteUrl = 'http://www.localhost:3000'
  17. const filestoreUrl = 'filestore.sharelatex.com'
  18. const projectArchiverUrl = 'project-archiver.overleaf.com'
  19. describe('TpdsUpdateSender', function () {
  20. beforeEach(function () {
  21. this.fakeUser = {
  22. _id: '12390i',
  23. }
  24. this.memberIds = [userId, collaberatorRef, readOnlyRef]
  25. this.enqueueUrl = new URL(
  26. 'http://tpdsworker/enqueue/web_to_tpds_http_requests'
  27. )
  28. this.CollaboratorsGetter = {
  29. promises: {
  30. getInvitedMemberIds: sinon.stub().resolves(this.memberIds),
  31. },
  32. }
  33. this.docstoreUrl = 'docstore.sharelatex.env'
  34. this.response = {
  35. ok: true,
  36. json: sinon.stub(),
  37. }
  38. this.FetchUtils = {
  39. fetchNothing: sinon.stub().resolves(),
  40. }
  41. this.settings = {
  42. siteUrl,
  43. apis: {
  44. thirdPartyDataStore: { url: thirdPartyDataStoreApiUrl },
  45. filestore: {
  46. url: filestoreUrl,
  47. },
  48. docstore: {
  49. pubUrl: this.docstoreUrl,
  50. },
  51. },
  52. }
  53. const getUsers = sinon.stub()
  54. getUsers
  55. .withArgs({
  56. _id: {
  57. $in: this.memberIds,
  58. },
  59. 'dropbox.access_token.uid': { $ne: null },
  60. })
  61. .resolves(
  62. this.memberIds.map(userId => {
  63. return { _id: userId }
  64. })
  65. )
  66. this.UserGetter = {
  67. promises: { getUsers },
  68. }
  69. this.TpdsUpdateSender = SandboxedModule.require(modulePath, {
  70. requires: {
  71. mongodb: { ObjectId },
  72. '@overleaf/settings': this.settings,
  73. '@overleaf/fetch-utils': this.FetchUtils,
  74. '../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
  75. '../User/UserGetter.js': this.UserGetter,
  76. '@overleaf/metrics': {
  77. inc() {},
  78. },
  79. },
  80. })
  81. })
  82. describe('enqueue', function () {
  83. it('should not call request if there is no tpdsworker url', async function () {
  84. await this.TpdsUpdateSender.promises.enqueue(null, null, null)
  85. this.FetchUtils.fetchNothing.should.not.have.been.called
  86. })
  87. it('should post the message to the tpdsworker', async function () {
  88. this.settings.apis.tpdsworker = { url: 'http://tpdsworker' }
  89. const group0 = 'myproject'
  90. const method0 = 'somemethod0'
  91. const job0 = 'do something'
  92. await this.TpdsUpdateSender.promises.enqueue(group0, method0, job0)
  93. this.FetchUtils.fetchNothing.should.have.been.calledWithMatch(
  94. this.enqueueUrl,
  95. {
  96. method: 'POST',
  97. json: { group: group0, job: job0, method: method0 },
  98. }
  99. )
  100. })
  101. })
  102. describe('sending updates', function () {
  103. beforeEach(function () {
  104. this.settings.apis.tpdsworker = { url: 'http://tpdsworker' }
  105. })
  106. it('queues a post the file with user and file id', async function () {
  107. const fileId = '4545345'
  108. const path = '/some/path/here.jpg'
  109. await this.TpdsUpdateSender.promises.addFile({
  110. projectId,
  111. fileId,
  112. path,
  113. projectName,
  114. })
  115. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  116. this.enqueueUrl,
  117. {
  118. json: {
  119. group: userId,
  120. method: 'pipeStreamFrom',
  121. job: {
  122. method: 'post',
  123. streamOrigin: `${filestoreUrl}/project/${projectId}/file/${fileId}`,
  124. uri: `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
  125. projectName
  126. )}${encodeURIComponent(path)}`,
  127. headers: {
  128. sl_all_user_ids: JSON.stringify([userId]),
  129. sl_project_owner_user_id: userId,
  130. },
  131. },
  132. },
  133. }
  134. )
  135. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  136. this.enqueueUrl,
  137. {
  138. json: {
  139. group: collaberatorRef,
  140. job: {
  141. headers: {
  142. sl_all_user_ids: JSON.stringify([collaberatorRef]),
  143. sl_project_owner_user_id: userId,
  144. },
  145. },
  146. },
  147. }
  148. )
  149. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  150. this.enqueueUrl,
  151. {
  152. json: {
  153. group: readOnlyRef,
  154. job: {
  155. headers: {
  156. sl_all_user_ids: JSON.stringify([readOnlyRef]),
  157. sl_project_owner_user_id: userId,
  158. },
  159. },
  160. },
  161. }
  162. )
  163. })
  164. it('post doc with stream origin of docstore', async function () {
  165. const docId = '4545345'
  166. const path = '/some/path/here.tex'
  167. const lines = ['line1', 'line2', 'line3']
  168. await this.TpdsUpdateSender.promises.addDoc({
  169. projectId,
  170. docId,
  171. path,
  172. docLines: lines,
  173. projectName,
  174. })
  175. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  176. this.enqueueUrl,
  177. {
  178. json: {
  179. group: userId,
  180. method: 'pipeStreamFrom',
  181. job: {
  182. method: 'post',
  183. uri: `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
  184. projectName
  185. )}${encodeURIComponent(path)}`,
  186. streamOrigin: `${this.docstoreUrl}/project/${projectId}/doc/${docId}/raw`,
  187. headers: {
  188. sl_all_user_ids: JSON.stringify([userId]),
  189. },
  190. },
  191. },
  192. }
  193. )
  194. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  195. this.enqueueUrl,
  196. {
  197. json: {
  198. group: collaberatorRef,
  199. job: {
  200. headers: {
  201. sl_all_user_ids: JSON.stringify([collaberatorRef]),
  202. },
  203. },
  204. },
  205. }
  206. )
  207. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  208. this.enqueueUrl,
  209. {
  210. json: {
  211. group: readOnlyRef,
  212. job: {
  213. headers: {
  214. sl_all_user_ids: JSON.stringify([readOnlyRef]),
  215. },
  216. },
  217. },
  218. }
  219. )
  220. })
  221. it('deleting entity', async function () {
  222. const path = '/path/here/t.tex'
  223. const subtreeEntityIds = ['id1', 'id2']
  224. await this.TpdsUpdateSender.promises.deleteEntity({
  225. projectId,
  226. path,
  227. projectName,
  228. subtreeEntityIds,
  229. })
  230. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  231. this.enqueueUrl,
  232. {
  233. json: {
  234. group: userId,
  235. method: 'standardHttpRequest',
  236. job: {
  237. method: 'delete',
  238. uri: `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
  239. projectName
  240. )}${encodeURIComponent(path)}`,
  241. headers: {
  242. sl_all_user_ids: JSON.stringify([userId]),
  243. },
  244. json: { subtreeEntityIds },
  245. },
  246. },
  247. }
  248. )
  249. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  250. this.enqueueUrl,
  251. {
  252. json: {
  253. group: collaberatorRef,
  254. job: {
  255. headers: {
  256. sl_all_user_ids: JSON.stringify([collaberatorRef]),
  257. },
  258. },
  259. },
  260. }
  261. )
  262. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  263. this.enqueueUrl,
  264. {
  265. json: {
  266. group: readOnlyRef,
  267. job: {
  268. headers: {
  269. sl_all_user_ids: JSON.stringify([readOnlyRef]),
  270. },
  271. },
  272. },
  273. }
  274. )
  275. })
  276. it('moving entity', async function () {
  277. const startPath = 'staring/here/file.tex'
  278. const endPath = 'ending/here/file.tex'
  279. await this.TpdsUpdateSender.promises.moveEntity({
  280. projectId,
  281. startPath,
  282. endPath,
  283. projectName,
  284. })
  285. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  286. this.enqueueUrl,
  287. {
  288. json: {
  289. group: userId,
  290. method: 'standardHttpRequest',
  291. job: {
  292. method: 'put',
  293. uri: `${thirdPartyDataStoreApiUrl}/user/${userId}/entity`,
  294. json: {
  295. startPath: `/${projectName}/${startPath}`,
  296. endPath: `/${projectName}/${endPath}`,
  297. },
  298. headers: {
  299. sl_all_user_ids: JSON.stringify([userId]),
  300. },
  301. },
  302. },
  303. }
  304. )
  305. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  306. this.enqueueUrl,
  307. {
  308. json: {
  309. group: collaberatorRef,
  310. job: {
  311. headers: {
  312. sl_all_user_ids: JSON.stringify([collaberatorRef]),
  313. },
  314. },
  315. },
  316. }
  317. )
  318. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  319. this.enqueueUrl,
  320. {
  321. json: {
  322. group: readOnlyRef,
  323. job: {
  324. headers: {
  325. sl_all_user_ids: JSON.stringify([readOnlyRef]),
  326. },
  327. },
  328. },
  329. }
  330. )
  331. })
  332. it('should be able to rename a project using the move entity func', async function () {
  333. const oldProjectName = '/oldProjectName/'
  334. const newProjectName = '/newProjectName/'
  335. await this.TpdsUpdateSender.promises.moveEntity({
  336. projectId,
  337. projectName: oldProjectName,
  338. newProjectName,
  339. })
  340. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  341. this.enqueueUrl,
  342. {
  343. json: {
  344. group: userId,
  345. method: 'standardHttpRequest',
  346. job: {
  347. method: 'put',
  348. uri: `${thirdPartyDataStoreApiUrl}/user/${userId}/entity`,
  349. json: {
  350. startPath: oldProjectName,
  351. endPath: newProjectName,
  352. },
  353. headers: {
  354. sl_all_user_ids: JSON.stringify([userId]),
  355. },
  356. },
  357. },
  358. }
  359. )
  360. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  361. this.enqueueUrl,
  362. {
  363. json: {
  364. group: collaberatorRef,
  365. job: {
  366. headers: {
  367. sl_all_user_ids: JSON.stringify([collaberatorRef]),
  368. },
  369. },
  370. },
  371. }
  372. )
  373. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  374. this.enqueueUrl,
  375. {
  376. json: {
  377. group: readOnlyRef,
  378. job: {
  379. headers: {
  380. sl_all_user_ids: JSON.stringify([readOnlyRef]),
  381. },
  382. },
  383. },
  384. }
  385. )
  386. })
  387. it('pollDropboxForUser', async function () {
  388. await this.TpdsUpdateSender.promises.pollDropboxForUser(userId)
  389. expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
  390. this.enqueueUrl,
  391. {
  392. json: {
  393. group: userId,
  394. method: 'standardHttpRequest',
  395. job: {
  396. method: 'post',
  397. uri: `${thirdPartyDataStoreApiUrl}/user/poll`,
  398. json: {
  399. user_ids: [userId],
  400. },
  401. },
  402. },
  403. }
  404. )
  405. })
  406. })
  407. describe('deleteProject', function () {
  408. it('should not call request if there is no project archiver url', async function () {
  409. await this.TpdsUpdateSender.promises.deleteProject({ projectId })
  410. this.FetchUtils.fetchNothing.should.not.have.been.called
  411. })
  412. it('should make a delete request to project archiver', async function () {
  413. this.settings.apis.project_archiver = { url: projectArchiverUrl }
  414. await this.TpdsUpdateSender.promises.deleteProject({ projectId })
  415. this.FetchUtils.fetchNothing.should.have.been.calledWith(
  416. `${projectArchiverUrl}/project/${projectId}`,
  417. { method: 'DELETE' }
  418. )
  419. })
  420. })
  421. describe('user not linked to dropbox', function () {
  422. beforeEach(function () {
  423. this.UserGetter.promises.getUsers
  424. .withArgs({
  425. _id: {
  426. $in: this.memberIds,
  427. },
  428. 'dropbox.access_token.uid': { $ne: null },
  429. })
  430. .resolves([])
  431. })
  432. })
  433. it('does not make request to tpds', async function () {
  434. const fileId = '4545345'
  435. const path = '/some/path/here.jpg'
  436. await this.TpdsUpdateSender.promises.addFile({
  437. projectId,
  438. fileId,
  439. path,
  440. projectName,
  441. })
  442. this.FetchUtils.fetchNothing.should.not.have.been.called
  443. })
  444. })