ApplyingUpdatesToProjectStructureTests.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-return-assign,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const sinon = require('sinon')
  15. const chai = require('chai')
  16. chai.should()
  17. const Settings = require('settings-sharelatex')
  18. const rclient_project_history = require('redis-sharelatex').createClient(
  19. Settings.redis.project_history
  20. )
  21. const ProjectHistoryKeys = Settings.redis.project_history.key_schema
  22. const MockProjectHistoryApi = require('./helpers/MockProjectHistoryApi')
  23. const MockWebApi = require('./helpers/MockWebApi')
  24. const DocUpdaterClient = require('./helpers/DocUpdaterClient')
  25. const DocUpdaterApp = require('./helpers/DocUpdaterApp')
  26. describe("Applying updates to a project's structure", function () {
  27. before(function () {
  28. this.user_id = 'user-id-123'
  29. return (this.version = 1234)
  30. })
  31. describe('renaming a file', function () {
  32. before(function (done) {
  33. this.project_id = DocUpdaterClient.randomId()
  34. this.fileUpdate = {
  35. id: DocUpdaterClient.randomId(),
  36. pathname: '/file-path',
  37. newPathname: '/new-file-path'
  38. }
  39. this.fileUpdates = [this.fileUpdate]
  40. return DocUpdaterApp.ensureRunning((error) => {
  41. if (error != null) {
  42. throw error
  43. }
  44. return DocUpdaterClient.sendProjectUpdate(
  45. this.project_id,
  46. this.user_id,
  47. [],
  48. this.fileUpdates,
  49. this.version,
  50. (error) => {
  51. if (error != null) {
  52. throw error
  53. }
  54. return setTimeout(done, 200)
  55. }
  56. )
  57. })
  58. })
  59. return it('should push the applied file renames to the project history api', function (done) {
  60. rclient_project_history.lrange(
  61. ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
  62. 0,
  63. -1,
  64. (error, updates) => {
  65. if (error != null) {
  66. throw error
  67. }
  68. const update = JSON.parse(updates[0])
  69. update.file.should.equal(this.fileUpdate.id)
  70. update.pathname.should.equal('/file-path')
  71. update.new_pathname.should.equal('/new-file-path')
  72. update.meta.user_id.should.equal(this.user_id)
  73. update.meta.ts.should.be.a('string')
  74. update.version.should.equal(`${this.version}.0`)
  75. return done()
  76. }
  77. )
  78. return null
  79. })
  80. })
  81. describe('renaming a document', function () {
  82. before(function () {
  83. this.docUpdate = {
  84. id: DocUpdaterClient.randomId(),
  85. pathname: '/doc-path',
  86. newPathname: '/new-doc-path'
  87. }
  88. return (this.docUpdates = [this.docUpdate])
  89. })
  90. describe('when the document is not loaded', function () {
  91. before(function (done) {
  92. this.project_id = DocUpdaterClient.randomId()
  93. DocUpdaterClient.sendProjectUpdate(
  94. this.project_id,
  95. this.user_id,
  96. this.docUpdates,
  97. [],
  98. this.version,
  99. (error) => {
  100. if (error != null) {
  101. throw error
  102. }
  103. return setTimeout(done, 200)
  104. }
  105. )
  106. return null
  107. })
  108. return it('should push the applied doc renames to the project history api', function (done) {
  109. rclient_project_history.lrange(
  110. ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
  111. 0,
  112. -1,
  113. (error, updates) => {
  114. if (error != null) {
  115. throw error
  116. }
  117. const update = JSON.parse(updates[0])
  118. update.doc.should.equal(this.docUpdate.id)
  119. update.pathname.should.equal('/doc-path')
  120. update.new_pathname.should.equal('/new-doc-path')
  121. update.meta.user_id.should.equal(this.user_id)
  122. update.meta.ts.should.be.a('string')
  123. update.version.should.equal(`${this.version}.0`)
  124. return done()
  125. }
  126. )
  127. return null
  128. })
  129. })
  130. return describe('when the document is loaded', function () {
  131. before(function (done) {
  132. this.project_id = DocUpdaterClient.randomId()
  133. MockWebApi.insertDoc(this.project_id, this.docUpdate.id, {})
  134. DocUpdaterClient.preloadDoc(
  135. this.project_id,
  136. this.docUpdate.id,
  137. (error) => {
  138. if (error != null) {
  139. throw error
  140. }
  141. sinon.spy(MockWebApi, 'getDocument')
  142. return DocUpdaterClient.sendProjectUpdate(
  143. this.project_id,
  144. this.user_id,
  145. this.docUpdates,
  146. [],
  147. this.version,
  148. (error) => {
  149. if (error != null) {
  150. throw error
  151. }
  152. return setTimeout(done, 200)
  153. }
  154. )
  155. }
  156. )
  157. return null
  158. })
  159. after(function () {
  160. return MockWebApi.getDocument.restore()
  161. })
  162. it('should update the doc', function (done) {
  163. DocUpdaterClient.getDoc(
  164. this.project_id,
  165. this.docUpdate.id,
  166. (error, res, doc) => {
  167. doc.pathname.should.equal(this.docUpdate.newPathname)
  168. return done()
  169. }
  170. )
  171. return null
  172. })
  173. return it('should push the applied doc renames to the project history api', function (done) {
  174. rclient_project_history.lrange(
  175. ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
  176. 0,
  177. -1,
  178. (error, updates) => {
  179. if (error != null) {
  180. throw error
  181. }
  182. const update = JSON.parse(updates[0])
  183. update.doc.should.equal(this.docUpdate.id)
  184. update.pathname.should.equal('/doc-path')
  185. update.new_pathname.should.equal('/new-doc-path')
  186. update.meta.user_id.should.equal(this.user_id)
  187. update.meta.ts.should.be.a('string')
  188. update.version.should.equal(`${this.version}.0`)
  189. return done()
  190. }
  191. )
  192. return null
  193. })
  194. })
  195. })
  196. describe('renaming multiple documents and files', function () {
  197. before(function () {
  198. this.docUpdate0 = {
  199. id: DocUpdaterClient.randomId(),
  200. pathname: '/doc-path0',
  201. newPathname: '/new-doc-path0'
  202. }
  203. this.docUpdate1 = {
  204. id: DocUpdaterClient.randomId(),
  205. pathname: '/doc-path1',
  206. newPathname: '/new-doc-path1'
  207. }
  208. this.docUpdates = [this.docUpdate0, this.docUpdate1]
  209. this.fileUpdate0 = {
  210. id: DocUpdaterClient.randomId(),
  211. pathname: '/file-path0',
  212. newPathname: '/new-file-path0'
  213. }
  214. this.fileUpdate1 = {
  215. id: DocUpdaterClient.randomId(),
  216. pathname: '/file-path1',
  217. newPathname: '/new-file-path1'
  218. }
  219. return (this.fileUpdates = [this.fileUpdate0, this.fileUpdate1])
  220. })
  221. return describe('when the documents are not loaded', function () {
  222. before(function (done) {
  223. this.project_id = DocUpdaterClient.randomId()
  224. DocUpdaterClient.sendProjectUpdate(
  225. this.project_id,
  226. this.user_id,
  227. this.docUpdates,
  228. this.fileUpdates,
  229. this.version,
  230. (error) => {
  231. if (error != null) {
  232. throw error
  233. }
  234. return setTimeout(done, 200)
  235. }
  236. )
  237. return null
  238. })
  239. return it('should push the applied doc renames to the project history api', function (done) {
  240. rclient_project_history.lrange(
  241. ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
  242. 0,
  243. -1,
  244. (error, updates) => {
  245. if (error != null) {
  246. throw error
  247. }
  248. let update = JSON.parse(updates[0])
  249. update.doc.should.equal(this.docUpdate0.id)
  250. update.pathname.should.equal('/doc-path0')
  251. update.new_pathname.should.equal('/new-doc-path0')
  252. update.meta.user_id.should.equal(this.user_id)
  253. update.meta.ts.should.be.a('string')
  254. update.version.should.equal(`${this.version}.0`)
  255. update = JSON.parse(updates[1])
  256. update.doc.should.equal(this.docUpdate1.id)
  257. update.pathname.should.equal('/doc-path1')
  258. update.new_pathname.should.equal('/new-doc-path1')
  259. update.meta.user_id.should.equal(this.user_id)
  260. update.meta.ts.should.be.a('string')
  261. update.version.should.equal(`${this.version}.1`)
  262. update = JSON.parse(updates[2])
  263. update.file.should.equal(this.fileUpdate0.id)
  264. update.pathname.should.equal('/file-path0')
  265. update.new_pathname.should.equal('/new-file-path0')
  266. update.meta.user_id.should.equal(this.user_id)
  267. update.meta.ts.should.be.a('string')
  268. update.version.should.equal(`${this.version}.2`)
  269. update = JSON.parse(updates[3])
  270. update.file.should.equal(this.fileUpdate1.id)
  271. update.pathname.should.equal('/file-path1')
  272. update.new_pathname.should.equal('/new-file-path1')
  273. update.meta.user_id.should.equal(this.user_id)
  274. update.meta.ts.should.be.a('string')
  275. update.version.should.equal(`${this.version}.3`)
  276. return done()
  277. }
  278. )
  279. return null
  280. })
  281. })
  282. })
  283. describe('adding a file', function () {
  284. before(function (done) {
  285. this.project_id = DocUpdaterClient.randomId()
  286. this.fileUpdate = {
  287. id: DocUpdaterClient.randomId(),
  288. pathname: '/file-path',
  289. url: 'filestore.example.com'
  290. }
  291. this.fileUpdates = [this.fileUpdate]
  292. DocUpdaterClient.sendProjectUpdate(
  293. this.project_id,
  294. this.user_id,
  295. [],
  296. this.fileUpdates,
  297. this.version,
  298. (error) => {
  299. if (error != null) {
  300. throw error
  301. }
  302. return setTimeout(done, 200)
  303. }
  304. )
  305. return null
  306. })
  307. return it('should push the file addition to the project history api', function (done) {
  308. rclient_project_history.lrange(
  309. ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
  310. 0,
  311. -1,
  312. (error, updates) => {
  313. if (error != null) {
  314. throw error
  315. }
  316. const update = JSON.parse(updates[0])
  317. update.file.should.equal(this.fileUpdate.id)
  318. update.pathname.should.equal('/file-path')
  319. update.url.should.equal('filestore.example.com')
  320. update.meta.user_id.should.equal(this.user_id)
  321. update.meta.ts.should.be.a('string')
  322. update.version.should.equal(`${this.version}.0`)
  323. return done()
  324. }
  325. )
  326. return null
  327. })
  328. })
  329. describe('adding a doc', function () {
  330. before(function (done) {
  331. this.project_id = DocUpdaterClient.randomId()
  332. this.docUpdate = {
  333. id: DocUpdaterClient.randomId(),
  334. pathname: '/file-path',
  335. docLines: 'a\nb'
  336. }
  337. this.docUpdates = [this.docUpdate]
  338. DocUpdaterClient.sendProjectUpdate(
  339. this.project_id,
  340. this.user_id,
  341. this.docUpdates,
  342. [],
  343. this.version,
  344. (error) => {
  345. if (error != null) {
  346. throw error
  347. }
  348. return setTimeout(done, 200)
  349. }
  350. )
  351. return null
  352. })
  353. return it('should push the doc addition to the project history api', function (done) {
  354. rclient_project_history.lrange(
  355. ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
  356. 0,
  357. -1,
  358. (error, updates) => {
  359. if (error != null) {
  360. throw error
  361. }
  362. const update = JSON.parse(updates[0])
  363. update.doc.should.equal(this.docUpdate.id)
  364. update.pathname.should.equal('/file-path')
  365. update.docLines.should.equal('a\nb')
  366. update.meta.user_id.should.equal(this.user_id)
  367. update.meta.ts.should.be.a('string')
  368. update.version.should.equal(`${this.version}.0`)
  369. return done()
  370. }
  371. )
  372. return null
  373. })
  374. })
  375. describe('with enough updates to flush to the history service', function () {
  376. before(function (done) {
  377. this.project_id = DocUpdaterClient.randomId()
  378. this.user_id = DocUpdaterClient.randomId()
  379. this.version0 = 12345
  380. this.version1 = this.version0 + 1
  381. const updates = []
  382. for (let v = 0; v <= 599; v++) {
  383. // Should flush after 500 ops
  384. updates.push({
  385. id: DocUpdaterClient.randomId(),
  386. pathname: '/file-' + v,
  387. docLines: 'a\nb'
  388. })
  389. }
  390. sinon.spy(MockProjectHistoryApi, 'flushProject')
  391. // Send updates in chunks to causes multiple flushes
  392. const projectId = this.project_id
  393. const userId = this.project_id
  394. DocUpdaterClient.sendProjectUpdate(
  395. projectId,
  396. userId,
  397. updates.slice(0, 250),
  398. [],
  399. this.version0,
  400. function (error) {
  401. if (error != null) {
  402. throw error
  403. }
  404. return DocUpdaterClient.sendProjectUpdate(
  405. projectId,
  406. userId,
  407. updates.slice(250),
  408. [],
  409. this.version1,
  410. (error) => {
  411. if (error != null) {
  412. throw error
  413. }
  414. return setTimeout(done, 2000)
  415. }
  416. )
  417. }
  418. )
  419. return null
  420. })
  421. after(function () {
  422. return MockProjectHistoryApi.flushProject.restore()
  423. })
  424. return it('should flush project history', function () {
  425. return MockProjectHistoryApi.flushProject
  426. .calledWith(this.project_id)
  427. .should.equal(true)
  428. })
  429. })
  430. return describe('with too few updates to flush to the history service', function () {
  431. before(function (done) {
  432. this.project_id = DocUpdaterClient.randomId()
  433. this.user_id = DocUpdaterClient.randomId()
  434. this.version0 = 12345
  435. this.version1 = this.version0 + 1
  436. const updates = []
  437. for (let v = 0; v <= 42; v++) {
  438. // Should flush after 500 ops
  439. updates.push({
  440. id: DocUpdaterClient.randomId(),
  441. pathname: '/file-' + v,
  442. docLines: 'a\nb'
  443. })
  444. }
  445. sinon.spy(MockProjectHistoryApi, 'flushProject')
  446. // Send updates in chunks
  447. const projectId = this.project_id
  448. const userId = this.project_id
  449. DocUpdaterClient.sendProjectUpdate(
  450. projectId,
  451. userId,
  452. updates.slice(0, 10),
  453. [],
  454. this.version0,
  455. function (error) {
  456. if (error != null) {
  457. throw error
  458. }
  459. return DocUpdaterClient.sendProjectUpdate(
  460. projectId,
  461. userId,
  462. updates.slice(10),
  463. [],
  464. this.version1,
  465. (error) => {
  466. if (error != null) {
  467. throw error
  468. }
  469. return setTimeout(done, 2000)
  470. }
  471. )
  472. }
  473. )
  474. return null
  475. })
  476. after(function () {
  477. return MockProjectHistoryApi.flushProject.restore()
  478. })
  479. return it('should not flush project history', function () {
  480. return MockProjectHistoryApi.flushProject
  481. .calledWith(this.project_id)
  482. .should.equal(false)
  483. })
  484. })
  485. })