ApplyingUpdatesToProjectStructureTests.js 15 KB

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