ArchivingUpdatesTests.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /* eslint-disable
  2. no-undef,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS103: Rewrite code to no longer use __guard__
  11. * DS202: Simplify dynamic range loops
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const sinon = require('sinon')
  16. const { expect } = require('chai')
  17. const { db, ObjectId } = require('../../../app/js/mongodb')
  18. const Settings = require('@overleaf/settings')
  19. const request = require('request')
  20. const rclient = require('redis').createClient(Settings.redis.history) // Only works locally for now
  21. const TrackChangesApp = require('./helpers/TrackChangesApp')
  22. const TrackChangesClient = require('./helpers/TrackChangesClient')
  23. const MockDocStoreApi = require('./helpers/MockDocStoreApi')
  24. const MockWebApi = require('./helpers/MockWebApi')
  25. describe('Archiving updates', function () {
  26. before(function (done) {
  27. if (
  28. __guard__(
  29. __guard__(
  30. Settings != null ? Settings.trackchanges : undefined,
  31. x1 => x1.s3
  32. ),
  33. x => x.key.length
  34. ) < 1
  35. ) {
  36. const message = new Error('s3 keys not setup, this test setup will fail')
  37. return done(message)
  38. }
  39. return TrackChangesClient.waitForS3(done)
  40. })
  41. before(function (done) {
  42. this.now = Date.now()
  43. this.to = this.now
  44. this.user_id = ObjectId().toString()
  45. this.user_id_2 = ObjectId().toString()
  46. this.doc_id = ObjectId().toString()
  47. this.project_id = ObjectId().toString()
  48. this.minutes = 60 * 1000
  49. this.hours = 60 * this.minutes
  50. MockWebApi.projects[this.project_id] = {
  51. features: {
  52. versioning: true,
  53. },
  54. }
  55. sinon.spy(MockWebApi, 'getProjectDetails')
  56. MockWebApi.users[this.user_id] = this.user = {
  57. email: 'user@sharelatex.com',
  58. first_name: 'Leo',
  59. last_name: 'Lion',
  60. id: this.user_id,
  61. }
  62. sinon.spy(MockWebApi, 'getUserInfo')
  63. MockDocStoreApi.docs[this.doc_id] = this.doc = {
  64. _id: this.doc_id,
  65. project_id: this.project_id,
  66. }
  67. sinon.spy(MockDocStoreApi, 'getAllDoc')
  68. this.updates = []
  69. for (
  70. let i = 0, end = 512 + 10, asc = end >= 0;
  71. asc ? i <= end : i >= end;
  72. asc ? i++ : i--
  73. ) {
  74. this.updates.push({
  75. op: [{ i: 'a', p: 0 }],
  76. meta: { ts: this.now + (i - 2048) * this.hours, user_id: this.user_id },
  77. v: 2 * i + 1,
  78. })
  79. this.updates.push({
  80. op: [{ i: 'b', p: 0 }],
  81. meta: {
  82. ts: this.now + (i - 2048) * this.hours + 10 * this.minutes,
  83. user_id: this.user_id_2,
  84. },
  85. v: 2 * i + 2,
  86. })
  87. }
  88. TrackChangesApp.ensureRunning(() => {
  89. return TrackChangesClient.pushRawUpdates(
  90. this.project_id,
  91. this.doc_id,
  92. this.updates,
  93. error => {
  94. if (error != null) {
  95. throw error
  96. }
  97. return TrackChangesClient.flushDoc(
  98. this.project_id,
  99. this.doc_id,
  100. error => {
  101. if (error != null) {
  102. throw error
  103. }
  104. return done()
  105. }
  106. )
  107. }
  108. )
  109. })
  110. return null
  111. })
  112. after(function (done) {
  113. MockWebApi.getUserInfo.restore()
  114. return db.docHistory.deleteMany(
  115. { project_id: ObjectId(this.project_id) },
  116. () => {
  117. return db.docHistoryIndex.remove(
  118. { project_id: ObjectId(this.project_id) },
  119. () => {
  120. return TrackChangesClient.removeS3Doc(
  121. this.project_id,
  122. this.doc_id,
  123. done
  124. )
  125. }
  126. )
  127. }
  128. )
  129. })
  130. function testExportFeature() {
  131. describe('exporting the project', function () {
  132. before('fetch export', function (done) {
  133. TrackChangesClient.exportProject(
  134. this.project_id,
  135. (error, updates, userIds) => {
  136. if (error) {
  137. return done(error)
  138. }
  139. this.exportedUpdates = updates
  140. this.exportedUserIds = userIds
  141. done()
  142. }
  143. )
  144. })
  145. it('should include all the imported updates, with ids, sorted by timestamp', function () {
  146. // Add a safe guard for an empty array matching an empty export.
  147. expect(this.updates).to.have.length(1024 + 22)
  148. const expectedExportedUpdates = this.updates
  149. .slice()
  150. .reverse()
  151. .map(update => {
  152. // clone object, updates are created once in before handler
  153. const exportedUpdate = Object.assign({}, update)
  154. exportedUpdate.meta = Object.assign({}, update.meta)
  155. exportedUpdate.doc_id = this.doc_id
  156. exportedUpdate.project_id = this.project_id
  157. // This is for merged updates, which does not apply here.
  158. exportedUpdate.meta.start_ts = exportedUpdate.meta.end_ts =
  159. exportedUpdate.meta.ts
  160. delete exportedUpdate.meta.ts
  161. return exportedUpdate
  162. })
  163. expect(this.exportedUpdates).to.deep.equal(expectedExportedUpdates)
  164. expect(this.exportedUserIds).to.deep.equal([
  165. this.user_id,
  166. this.user_id_2,
  167. ])
  168. })
  169. })
  170. }
  171. describe("before archiving a doc's updates", function () {
  172. testExportFeature()
  173. })
  174. describe("archiving a doc's updates", function () {
  175. before(function (done) {
  176. TrackChangesClient.pushDocHistory(this.project_id, this.doc_id, error => {
  177. if (error != null) {
  178. throw error
  179. }
  180. return done()
  181. })
  182. return null
  183. })
  184. it('should have one cached pack', function (done) {
  185. return db.docHistory.count(
  186. { doc_id: ObjectId(this.doc_id), expiresAt: { $exists: true } },
  187. (error, count) => {
  188. if (error != null) {
  189. throw error
  190. }
  191. count.should.equal(1)
  192. return done()
  193. }
  194. )
  195. })
  196. it('should have one remaining pack after cache is expired', function (done) {
  197. return db.docHistory.deleteMany(
  198. {
  199. doc_id: ObjectId(this.doc_id),
  200. expiresAt: { $exists: true },
  201. },
  202. (err, result) => {
  203. if (err) return done(err)
  204. return db.docHistory.count(
  205. { doc_id: ObjectId(this.doc_id) },
  206. (error, count) => {
  207. if (error != null) {
  208. throw error
  209. }
  210. count.should.equal(1)
  211. return done()
  212. }
  213. )
  214. }
  215. )
  216. })
  217. it('should have a docHistoryIndex entry marked as inS3', function (done) {
  218. return db.docHistoryIndex.findOne(
  219. { _id: ObjectId(this.doc_id) },
  220. (error, index) => {
  221. if (error != null) {
  222. throw error
  223. }
  224. index.packs[0].inS3.should.equal(true)
  225. return done()
  226. }
  227. )
  228. })
  229. it('should have a docHistoryIndex entry with the last version', function (done) {
  230. return db.docHistoryIndex.findOne(
  231. { _id: ObjectId(this.doc_id) },
  232. (error, index) => {
  233. if (error != null) {
  234. throw error
  235. }
  236. index.packs[0].v_end.should.equal(1024)
  237. return done()
  238. }
  239. )
  240. })
  241. it('should store 1024 doc changes in S3 in one pack', function (done) {
  242. return db.docHistoryIndex.findOne(
  243. { _id: ObjectId(this.doc_id) },
  244. (error, index) => {
  245. if (error != null) {
  246. throw error
  247. }
  248. const packId = index.packs[0]._id
  249. return TrackChangesClient.getS3Doc(
  250. this.project_id,
  251. this.doc_id,
  252. packId,
  253. (error, doc) => {
  254. if (error) return done(error)
  255. doc.n.should.equal(1024)
  256. doc.pack.length.should.equal(1024)
  257. return done()
  258. }
  259. )
  260. }
  261. )
  262. })
  263. testExportFeature()
  264. })
  265. return describe("unarchiving a doc's updates", function () {
  266. before(function (done) {
  267. TrackChangesClient.pullDocHistory(this.project_id, this.doc_id, error => {
  268. if (error != null) {
  269. throw error
  270. }
  271. return done()
  272. })
  273. return null
  274. })
  275. return it('should restore both packs', function (done) {
  276. return db.docHistory.count(
  277. { doc_id: ObjectId(this.doc_id) },
  278. (error, count) => {
  279. if (error != null) {
  280. throw error
  281. }
  282. count.should.equal(2)
  283. return done()
  284. }
  285. )
  286. })
  287. })
  288. })
  289. function __guard__(value, transform) {
  290. return typeof value !== 'undefined' && value !== null
  291. ? transform(value)
  292. : undefined
  293. }