ApplyingUpdatesToProjectStructureTests.js 15 KB

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