DocumentUpdaterHandlerTests.js 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. const sinon = require('sinon')
  2. const SandboxedModule = require('sandboxed-module')
  3. const path = require('path')
  4. const { ObjectId } = require('mongojs')
  5. const modulePath = path.join(
  6. __dirname,
  7. '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler'
  8. )
  9. describe('DocumentUpdaterHandler', function() {
  10. beforeEach(function() {
  11. this.project_id = 'project-id-923'
  12. this.projectHistoryId = 'ol-project-id-1'
  13. this.doc_id = 'doc-id-394'
  14. this.lines = ['one', 'two', 'three']
  15. this.version = 42
  16. this.user_id = 'mock-user-id-123'
  17. this.project = { _id: this.project_id }
  18. this.request = sinon.stub()
  19. this.projectEntityHandler = {}
  20. this.settings = {
  21. apis: {
  22. documentupdater: {
  23. url: 'http://document_updater.example.com'
  24. },
  25. project_history: {
  26. url: 'http://project_history.example.com'
  27. }
  28. }
  29. }
  30. this.callback = sinon.stub()
  31. this.handler = SandboxedModule.require(modulePath, {
  32. globals: {
  33. console: console
  34. },
  35. requires: {
  36. request: {
  37. defaults: () => {
  38. return this.request
  39. }
  40. },
  41. 'settings-sharelatex': this.settings,
  42. 'logger-sharelatex': { log() {}, error() {}, warn() {} },
  43. '../Project/ProjectEntityHandler': this.projectEntityHandler,
  44. '../../models/Project': {
  45. Project: (this.Project = {})
  46. },
  47. '../../Features/Project/ProjectLocator': {},
  48. 'metrics-sharelatex': {
  49. Timer: class {
  50. done() {}
  51. }
  52. }
  53. }
  54. })
  55. })
  56. describe('flushProjectToMongo', function() {
  57. describe('successfully', function() {
  58. beforeEach(function() {
  59. this.request.callsArgWith(1, null, { statusCode: 204 }, '')
  60. this.handler.flushProjectToMongo(this.project_id, this.callback)
  61. })
  62. it('should flush the document from the document updater', function() {
  63. this.request
  64. .calledWithMatch({
  65. url: `${this.settings.apis.documentupdater.url}/project/${
  66. this.project_id
  67. }/flush`,
  68. method: 'POST'
  69. })
  70. .should.equal(true)
  71. })
  72. it('should call the callback with no error', function() {
  73. this.callback.calledWith(null).should.equal(true)
  74. })
  75. })
  76. describe('when the document updater API returns an error', function() {
  77. beforeEach(function() {
  78. this.request.callsArgWith(
  79. 1,
  80. (this.error = new Error('something went wrong')),
  81. null,
  82. null
  83. )
  84. this.handler.flushProjectToMongo(this.project_id, this.callback)
  85. })
  86. it('should return an error to the callback', function() {
  87. this.callback.calledWith(this.error).should.equal(true)
  88. })
  89. })
  90. describe('when the document updater returns a failure error code', function() {
  91. beforeEach(function() {
  92. this.request.callsArgWith(1, null, { statusCode: 500 }, '')
  93. this.handler.flushProjectToMongo(this.project_id, this.callback)
  94. })
  95. it('should return the callback with an error', function() {
  96. this.callback
  97. .calledWith(
  98. new Error('doc updater returned failure status code: 500')
  99. )
  100. .should.equal(true)
  101. })
  102. })
  103. })
  104. describe('flushProjectToMongoAndDelete', function() {
  105. describe('successfully', function() {
  106. beforeEach(function() {
  107. this.request.callsArgWith(1, null, { statusCode: 204 }, '')
  108. this.handler.flushProjectToMongoAndDelete(
  109. this.project_id,
  110. this.callback
  111. )
  112. })
  113. it('should delete the project from the document updater', function() {
  114. this.request
  115. .calledWithMatch({
  116. url: `${this.settings.apis.documentupdater.url}/project/${
  117. this.project_id
  118. }`,
  119. method: 'DELETE'
  120. })
  121. .should.equal(true)
  122. })
  123. it('should call the callback with no error', function() {
  124. this.callback.calledWith(null).should.equal(true)
  125. })
  126. })
  127. describe('when the document updater API returns an error', function() {
  128. beforeEach(function() {
  129. this.request.callsArgWith(
  130. 1,
  131. (this.error = new Error('something went wrong')),
  132. null,
  133. null
  134. )
  135. this.handler.flushProjectToMongoAndDelete(
  136. this.project_id,
  137. this.callback
  138. )
  139. })
  140. it('should return an error to the callback', function() {
  141. this.callback.calledWith(this.error).should.equal(true)
  142. })
  143. })
  144. describe('when the document updater returns a failure error code', function() {
  145. beforeEach(function() {
  146. this.request.callsArgWith(1, null, { statusCode: 500 }, '')
  147. this.handler.flushProjectToMongoAndDelete(
  148. this.project_id,
  149. this.callback
  150. )
  151. })
  152. it('should return the callback with an error', function() {
  153. this.callback
  154. .calledWith(
  155. new Error('doc updater returned failure status code: 500')
  156. )
  157. .should.equal(true)
  158. })
  159. })
  160. })
  161. describe('flushDocToMongo', function() {
  162. describe('successfully', function() {
  163. beforeEach(function() {
  164. this.request.callsArgWith(1, null, { statusCode: 204 }, '')
  165. this.handler.flushDocToMongo(
  166. this.project_id,
  167. this.doc_id,
  168. this.callback
  169. )
  170. })
  171. it('should flush the document from the document updater', function() {
  172. this.request
  173. .calledWithMatch({
  174. url: `${this.settings.apis.documentupdater.url}/project/${
  175. this.project_id
  176. }/doc/${this.doc_id}/flush`,
  177. method: 'POST'
  178. })
  179. .should.equal(true)
  180. })
  181. it('should call the callback with no error', function() {
  182. this.callback.calledWith(null).should.equal(true)
  183. })
  184. })
  185. describe('when the document updater API returns an error', function() {
  186. beforeEach(function() {
  187. this.request.callsArgWith(
  188. 1,
  189. (this.error = new Error('something went wrong')),
  190. null,
  191. null
  192. )
  193. this.handler.flushDocToMongo(
  194. this.project_id,
  195. this.doc_id,
  196. this.callback
  197. )
  198. })
  199. it('should return an error to the callback', function() {
  200. this.callback.calledWith(this.error).should.equal(true)
  201. })
  202. })
  203. describe('when the document updater returns a failure error code', function() {
  204. beforeEach(function() {
  205. this.request.callsArgWith(1, null, { statusCode: 500 }, '')
  206. this.handler.flushDocToMongo(
  207. this.project_id,
  208. this.doc_id,
  209. this.callback
  210. )
  211. })
  212. it('should return the callback with an error', function() {
  213. this.callback
  214. .calledWith(
  215. new Error('doc updater returned failure status code: 500')
  216. )
  217. .should.equal(true)
  218. })
  219. })
  220. })
  221. describe('deleteDoc', function() {
  222. describe('successfully', function() {
  223. beforeEach(function() {
  224. this.request.callsArgWith(1, null, { statusCode: 204 }, '')
  225. this.handler.deleteDoc(this.project_id, this.doc_id, this.callback)
  226. })
  227. it('should delete the document from the document updater', function() {
  228. this.request
  229. .calledWithMatch({
  230. url: `${this.settings.apis.documentupdater.url}/project/${
  231. this.project_id
  232. }/doc/${this.doc_id}`,
  233. method: 'DELETE'
  234. })
  235. .should.equal(true)
  236. })
  237. it('should call the callback with no error', function() {
  238. this.callback.calledWith(null).should.equal(true)
  239. })
  240. })
  241. describe('when the document updater API returns an error', function() {
  242. beforeEach(function() {
  243. this.request.callsArgWith(
  244. 1,
  245. (this.error = new Error('something went wrong')),
  246. null,
  247. null
  248. )
  249. this.handler.deleteDoc(this.project_id, this.doc_id, this.callback)
  250. })
  251. it('should return an error to the callback', function() {
  252. this.callback.calledWith(this.error).should.equal(true)
  253. })
  254. })
  255. describe('when the document updater returns a failure error code', function() {
  256. beforeEach(function() {
  257. this.request.callsArgWith(1, null, { statusCode: 500 }, '')
  258. this.handler.deleteDoc(this.project_id, this.doc_id, this.callback)
  259. })
  260. it('should return the callback with an error', function() {
  261. this.callback
  262. .calledWith(
  263. new Error('doc updater returned failure status code: 500')
  264. )
  265. .should.equal(true)
  266. })
  267. })
  268. })
  269. describe('setDocument', function() {
  270. beforeEach(function() {
  271. this.source = 'dropbox'
  272. })
  273. describe('successfully', function() {
  274. beforeEach(function() {
  275. this.request.callsArgWith(1, null, { statusCode: 204 }, '')
  276. this.handler.setDocument(
  277. this.project_id,
  278. this.doc_id,
  279. this.user_id,
  280. this.lines,
  281. this.source,
  282. this.callback
  283. )
  284. })
  285. it('should set the document in the document updater', function() {
  286. this.request
  287. .calledWith({
  288. url: `${this.settings.apis.documentupdater.url}/project/${
  289. this.project_id
  290. }/doc/${this.doc_id}`,
  291. json: {
  292. lines: this.lines,
  293. source: this.source,
  294. user_id: this.user_id
  295. },
  296. method: 'POST'
  297. })
  298. .should.equal(true)
  299. })
  300. it('should call the callback with no error', function() {
  301. this.callback.calledWith(null).should.equal(true)
  302. })
  303. })
  304. describe('when the document updater API returns an error', function() {
  305. beforeEach(function() {
  306. this.request.callsArgWith(
  307. 1,
  308. (this.error = new Error('something went wrong')),
  309. null,
  310. null
  311. )
  312. this.handler.setDocument(
  313. this.project_id,
  314. this.doc_id,
  315. this.user_id,
  316. this.lines,
  317. this.source,
  318. this.callback
  319. )
  320. })
  321. it('should return an error to the callback', function() {
  322. this.callback.calledWith(this.error).should.equal(true)
  323. })
  324. })
  325. describe('when the document updater returns a failure error code', function() {
  326. beforeEach(function() {
  327. this.request.callsArgWith(1, null, { statusCode: 500 }, '')
  328. this.handler.setDocument(
  329. this.project_id,
  330. this.doc_id,
  331. this.user_id,
  332. this.lines,
  333. this.source,
  334. this.callback
  335. )
  336. })
  337. it('should return the callback with an error', function() {
  338. this.callback
  339. .calledWith(
  340. new Error('doc updater returned failure status code: 500')
  341. )
  342. .should.equal(true)
  343. })
  344. })
  345. })
  346. describe('getDocument', function() {
  347. describe('successfully', function() {
  348. beforeEach(function() {
  349. this.body = {
  350. lines: this.lines,
  351. version: this.version,
  352. ops: (this.ops = ['mock-op-1', 'mock-op-2']),
  353. ranges: (this.ranges = { mock: 'ranges' })
  354. }
  355. this.fromVersion = 2
  356. this.request.callsArgWith(1, null, { statusCode: 200 }, this.body)
  357. this.handler.getDocument(
  358. this.project_id,
  359. this.doc_id,
  360. this.fromVersion,
  361. this.callback
  362. )
  363. })
  364. it('should get the document from the document updater', function() {
  365. this.request
  366. .calledWith({
  367. url: `${this.settings.apis.documentupdater.url}/project/${
  368. this.project_id
  369. }/doc/${this.doc_id}?fromVersion=${this.fromVersion}`,
  370. method: 'GET',
  371. json: true
  372. })
  373. .should.equal(true)
  374. })
  375. it('should call the callback with the lines and version', function() {
  376. this.callback
  377. .calledWith(null, this.lines, this.version, this.ranges, this.ops)
  378. .should.equal(true)
  379. })
  380. })
  381. describe('when the document updater API returns an error', function() {
  382. beforeEach(function() {
  383. this.request.callsArgWith(
  384. 1,
  385. (this.error = new Error('something went wrong')),
  386. null,
  387. null
  388. )
  389. this.handler.getDocument(
  390. this.project_id,
  391. this.doc_id,
  392. this.fromVersion,
  393. this.callback
  394. )
  395. })
  396. it('should return an error to the callback', function() {
  397. this.callback.calledWith(this.error).should.equal(true)
  398. })
  399. })
  400. describe('when the document updater returns a failure error code', function() {
  401. beforeEach(function() {
  402. this.request.callsArgWith(1, null, { statusCode: 500 }, '')
  403. this.handler.getDocument(
  404. this.project_id,
  405. this.doc_id,
  406. this.fromVersion,
  407. this.callback
  408. )
  409. })
  410. it('should return the callback with an error', function() {
  411. this.callback
  412. .calledWith(
  413. new Error('doc updater returned failure status code: 500')
  414. )
  415. .should.equal(true)
  416. })
  417. })
  418. })
  419. describe('getProjectDocsIfMatch', function() {
  420. beforeEach(function() {
  421. this.project_state_hash = '1234567890abcdef'
  422. })
  423. describe('successfully', function() {
  424. beforeEach(function() {
  425. this.doc0 = {
  426. _id: this.doc_id,
  427. lines: this.lines,
  428. v: this.version
  429. }
  430. this.docs = [this.doc0, this.doc0, this.doc0]
  431. this.body = JSON.stringify(this.docs)
  432. this.request.post = sinon
  433. .stub()
  434. .callsArgWith(1, null, { statusCode: 200 }, this.body)
  435. this.handler.getProjectDocsIfMatch(
  436. this.project_id,
  437. this.project_state_hash,
  438. this.callback
  439. )
  440. })
  441. it('should get the documents from the document updater', function() {
  442. const url = `${this.settings.apis.documentupdater.url}/project/${
  443. this.project_id
  444. }/get_and_flush_if_old?state=${this.project_state_hash}`
  445. this.request.post.calledWith(url).should.equal(true)
  446. })
  447. it('should call the callback with the documents', function() {
  448. this.callback.calledWithExactly(null, this.docs).should.equal(true)
  449. })
  450. })
  451. describe('when the document updater API returns an error', function() {
  452. beforeEach(function() {
  453. this.request.post = sinon
  454. .stub()
  455. .callsArgWith(
  456. 1,
  457. (this.error = new Error('something went wrong')),
  458. null,
  459. null
  460. )
  461. this.handler.getProjectDocsIfMatch(
  462. this.project_id,
  463. this.project_state_hash,
  464. this.callback
  465. )
  466. })
  467. it('should return an error to the callback', function() {
  468. this.callback.calledWith(this.error).should.equal(true)
  469. })
  470. })
  471. describe('when the document updater returns a conflict error code', function() {
  472. beforeEach(function() {
  473. this.request.post = sinon
  474. .stub()
  475. .callsArgWith(1, null, { statusCode: 409 }, 'Conflict')
  476. this.handler.getProjectDocsIfMatch(
  477. this.project_id,
  478. this.project_state_hash,
  479. this.callback
  480. )
  481. })
  482. it('should return the callback with no documents', function() {
  483. this.callback.alwaysCalledWithExactly().should.equal(true)
  484. })
  485. })
  486. })
  487. describe('clearProjectState', function() {
  488. describe('successfully', function() {
  489. beforeEach(function() {
  490. this.request.callsArgWith(1, null, { statusCode: 200 })
  491. this.handler.clearProjectState(this.project_id, this.callback)
  492. })
  493. it('should clear the project state from the document updater', function() {
  494. this.request
  495. .calledWithMatch({
  496. url: `${this.settings.apis.documentupdater.url}/project/${
  497. this.project_id
  498. }/clearState`,
  499. method: 'POST'
  500. })
  501. .should.equal(true)
  502. })
  503. it('should call the callback', function() {
  504. this.callback.calledWith(null).should.equal(true)
  505. })
  506. })
  507. describe('when the document updater API returns an error', function() {
  508. beforeEach(function() {
  509. this.request.callsArgWith(
  510. 1,
  511. (this.error = new Error('something went wrong')),
  512. null,
  513. null
  514. )
  515. this.handler.clearProjectState(this.project_id, this.callback)
  516. })
  517. it('should return an error to the callback', function() {
  518. this.callback.calledWith(this.error).should.equal(true)
  519. })
  520. })
  521. describe('when the document updater returns an error code', function() {
  522. beforeEach(function() {
  523. this.request.callsArgWith(1, null, { statusCode: 500 }, null)
  524. this.handler.clearProjectState(this.project_id, this.callback)
  525. })
  526. it('should return the callback with no documents', function() {
  527. this.callback
  528. .calledWith(
  529. new Error('doc updater returned failure status code: 500')
  530. )
  531. .should.equal(true)
  532. })
  533. })
  534. })
  535. describe('acceptChanges', function() {
  536. beforeEach(function() {
  537. this.change_id = 'mock-change-id-1'
  538. })
  539. describe('successfully', function() {
  540. beforeEach(function() {
  541. this.request.callsArgWith(1, null, { statusCode: 200 }, this.body)
  542. this.handler.acceptChanges(
  543. this.project_id,
  544. this.doc_id,
  545. [this.change_id],
  546. this.callback
  547. )
  548. })
  549. it('should accept the change in the document updater', function() {
  550. this.request
  551. .calledWith({
  552. url: `${this.settings.apis.documentupdater.url}/project/${
  553. this.project_id
  554. }/doc/${this.doc_id}/change/accept`,
  555. json: {
  556. change_ids: [this.change_id]
  557. },
  558. method: 'POST'
  559. })
  560. .should.equal(true)
  561. })
  562. it('should call the callback', function() {
  563. this.callback.calledWith(null).should.equal(true)
  564. })
  565. })
  566. describe('when the document updater API returns an error', function() {
  567. beforeEach(function() {
  568. this.request.callsArgWith(
  569. 1,
  570. (this.error = new Error('something went wrong')),
  571. null,
  572. null
  573. )
  574. this.handler.acceptChanges(
  575. this.project_id,
  576. this.doc_id,
  577. [this.change_id],
  578. this.callback
  579. )
  580. })
  581. it('should return an error to the callback', function() {
  582. this.callback.calledWith(this.error).should.equal(true)
  583. })
  584. })
  585. describe('when the document updater returns a failure error code', function() {
  586. beforeEach(function() {
  587. this.request.callsArgWith(1, null, { statusCode: 500 }, '')
  588. this.handler.acceptChanges(
  589. this.project_id,
  590. this.doc_id,
  591. [this.change_id],
  592. this.callback
  593. )
  594. })
  595. it('should return the callback with an error', function() {
  596. this.callback
  597. .calledWith(
  598. new Error('doc updater returned failure status code: 500')
  599. )
  600. .should.equal(true)
  601. })
  602. })
  603. })
  604. describe('deleteThread', function() {
  605. beforeEach(function() {
  606. this.thread_id = 'mock-thread-id-1'
  607. })
  608. describe('successfully', function() {
  609. beforeEach(function() {
  610. this.request.callsArgWith(1, null, { statusCode: 200 }, this.body)
  611. this.handler.deleteThread(
  612. this.project_id,
  613. this.doc_id,
  614. this.thread_id,
  615. this.callback
  616. )
  617. })
  618. it('should delete the thread in the document updater', function() {
  619. this.request
  620. .calledWithMatch({
  621. url: `${this.settings.apis.documentupdater.url}/project/${
  622. this.project_id
  623. }/doc/${this.doc_id}/comment/${this.thread_id}`,
  624. method: 'DELETE'
  625. })
  626. .should.equal(true)
  627. })
  628. it('should call the callback', function() {
  629. this.callback.calledWith(null).should.equal(true)
  630. })
  631. })
  632. describe('when the document updater API returns an error', function() {
  633. beforeEach(function() {
  634. this.request.callsArgWith(
  635. 1,
  636. (this.error = new Error('something went wrong')),
  637. null,
  638. null
  639. )
  640. this.handler.deleteThread(
  641. this.project_id,
  642. this.doc_id,
  643. this.thread_id,
  644. this.callback
  645. )
  646. })
  647. it('should return an error to the callback', function() {
  648. this.callback.calledWith(this.error).should.equal(true)
  649. })
  650. })
  651. describe('when the document updater returns a failure error code', function() {
  652. beforeEach(function() {
  653. this.request.callsArgWith(1, null, { statusCode: 500 }, '')
  654. this.handler.deleteThread(
  655. this.project_id,
  656. this.doc_id,
  657. this.thread_id,
  658. this.callback
  659. )
  660. })
  661. it('should return the callback with an error', function() {
  662. this.callback
  663. .calledWith(
  664. new Error('doc updater returned failure status code: 500')
  665. )
  666. .should.equal(true)
  667. })
  668. })
  669. })
  670. describe('updateProjectStructure ', function() {
  671. beforeEach(function() {
  672. this.user_id = 1234
  673. this.version = 999
  674. })
  675. describe('with project history disabled', function() {
  676. beforeEach(function() {
  677. this.settings.apis.project_history.sendProjectStructureOps = false
  678. this.handler.updateProjectStructure(
  679. this.project_id,
  680. this.projectHistoryId,
  681. this.user_id,
  682. {},
  683. this.callback
  684. )
  685. })
  686. it('does not make a web request', function() {
  687. this.request.called.should.equal(false)
  688. })
  689. it('calls the callback', function() {
  690. this.callback.called.should.equal(true)
  691. })
  692. })
  693. describe('with project history enabled', function() {
  694. beforeEach(function() {
  695. this.settings.apis.project_history.sendProjectStructureOps = true
  696. this.url = `${this.settings.apis.documentupdater.url}/project/${
  697. this.project_id
  698. }`
  699. this.request.callsArgWith(1, null, { statusCode: 204 }, '')
  700. })
  701. describe('when an entity has changed name', function() {
  702. it('should send the structure update to the document updater', function(done) {
  703. this.docIdA = new ObjectId()
  704. this.docIdB = new ObjectId()
  705. this.changes = {
  706. oldDocs: [
  707. { path: '/old_a', doc: { _id: this.docIdA } },
  708. { path: '/old_b', doc: { _id: this.docIdB } }
  709. ],
  710. // create new instances of the same ObjectIds so that == doesn't pass
  711. newDocs: [
  712. {
  713. path: '/old_a',
  714. doc: { _id: new ObjectId(this.docIdA.toString()) }
  715. },
  716. {
  717. path: '/new_b',
  718. doc: { _id: new ObjectId(this.docIdB.toString()) }
  719. }
  720. ],
  721. newProject: { version: this.version }
  722. }
  723. const docUpdates = [
  724. {
  725. id: this.docIdB.toString(),
  726. pathname: '/old_b',
  727. newPathname: '/new_b'
  728. }
  729. ]
  730. this.handler.updateProjectStructure(
  731. this.project_id,
  732. this.projectHistoryId,
  733. this.user_id,
  734. this.changes,
  735. () => {
  736. this.request
  737. .calledWith({
  738. url: this.url,
  739. method: 'POST',
  740. json: {
  741. docUpdates,
  742. fileUpdates: [],
  743. userId: this.user_id,
  744. version: this.version,
  745. projectHistoryId: this.projectHistoryId
  746. }
  747. })
  748. .should.equal(true)
  749. done()
  750. }
  751. )
  752. })
  753. })
  754. describe('when a doc has been added', function() {
  755. it('should send the structure update to the document updater', function(done) {
  756. this.docId = new ObjectId()
  757. this.changes = {
  758. newDocs: [
  759. { path: '/foo', docLines: 'a\nb', doc: { _id: this.docId } }
  760. ],
  761. newProject: { version: this.version }
  762. }
  763. const docUpdates = [
  764. {
  765. id: this.docId.toString(),
  766. pathname: '/foo',
  767. docLines: 'a\nb',
  768. url: undefined,
  769. hash: undefined
  770. }
  771. ]
  772. this.handler.updateProjectStructure(
  773. this.project_id,
  774. this.projectHistoryId,
  775. this.user_id,
  776. this.changes,
  777. () => {
  778. this.request
  779. .calledWith({
  780. url: this.url,
  781. method: 'POST',
  782. json: {
  783. docUpdates,
  784. fileUpdates: [],
  785. userId: this.user_id,
  786. version: this.version,
  787. projectHistoryId: this.projectHistoryId
  788. }
  789. })
  790. .should.equal(true)
  791. done()
  792. }
  793. )
  794. })
  795. })
  796. describe('when a file has been added', function() {
  797. it('should send the structure update to the document updater', function(done) {
  798. this.fileId = new ObjectId()
  799. this.changes = {
  800. newFiles: [
  801. {
  802. path: '/bar',
  803. url: 'filestore.example.com/file',
  804. file: { _id: this.fileId, hash: '12345' }
  805. }
  806. ],
  807. newProject: { version: this.version }
  808. }
  809. const fileUpdates = [
  810. {
  811. id: this.fileId.toString(),
  812. pathname: '/bar',
  813. url: 'filestore.example.com/file',
  814. docLines: undefined,
  815. hash: '12345'
  816. }
  817. ]
  818. this.handler.updateProjectStructure(
  819. this.project_id,
  820. this.projectHistoryId,
  821. this.user_id,
  822. this.changes,
  823. () => {
  824. this.request
  825. .calledWith({
  826. url: this.url,
  827. method: 'POST',
  828. json: {
  829. docUpdates: [],
  830. fileUpdates,
  831. userId: this.user_id,
  832. version: this.version,
  833. projectHistoryId: this.projectHistoryId
  834. }
  835. })
  836. .should.equal(true)
  837. done()
  838. }
  839. )
  840. })
  841. })
  842. describe('when an entity has been deleted', function() {
  843. it('should end the structure update to the document updater', function(done) {
  844. this.docId = new ObjectId()
  845. this.changes = {
  846. oldDocs: [
  847. { path: '/foo', docLines: 'a\nb', doc: { _id: this.docId } }
  848. ],
  849. newProject: { version: this.version }
  850. }
  851. const docUpdates = [
  852. {
  853. id: this.docId.toString(),
  854. pathname: '/foo',
  855. newPathname: ''
  856. }
  857. ]
  858. this.handler.updateProjectStructure(
  859. this.project_id,
  860. this.projectHistoryId,
  861. this.user_id,
  862. this.changes,
  863. () => {
  864. this.request
  865. .calledWith({
  866. url: this.url,
  867. method: 'POST',
  868. json: {
  869. docUpdates,
  870. fileUpdates: [],
  871. userId: this.user_id,
  872. version: this.version,
  873. projectHistoryId: this.projectHistoryId
  874. }
  875. })
  876. .should.equal(true)
  877. done()
  878. }
  879. )
  880. })
  881. })
  882. describe('when the project version is missing', function() {
  883. it('should call the callback with an error', function() {
  884. this.docId = new ObjectId()
  885. this.changes = {
  886. oldDocs: [
  887. { path: '/foo', docLines: 'a\nb', doc: { _id: this.docId } }
  888. ]
  889. }
  890. this.handler.updateProjectStructure(
  891. this.project_id,
  892. this.projectHistoryId,
  893. this.user_id,
  894. this.changes,
  895. this.callback
  896. )
  897. this.callback.calledWith(new Error()).should.equal(true)
  898. const firstCallArgs = this.callback.args[0]
  899. firstCallArgs[0].message.should.equal(
  900. 'did not receive project version in changes'
  901. )
  902. })
  903. })
  904. })
  905. })
  906. })