CheckRedisMongoSyncStateTests.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. const MockWebApi = require('./helpers/MockWebApi')
  2. const DocUpdaterClient = require('./helpers/DocUpdaterClient')
  3. const DocUpdaterApp = require('./helpers/DocUpdaterApp')
  4. const { promisify } = require('util')
  5. const { exec } = require('child_process')
  6. const { expect } = require('chai')
  7. const Settings = require('@overleaf/settings')
  8. const fs = require('fs')
  9. const Path = require('path')
  10. const rclient = require('@overleaf/redis-wrapper').createClient(
  11. Settings.redis.documentupdater
  12. )
  13. describe('CheckRedisMongoSyncState', function () {
  14. beforeEach(function (done) {
  15. DocUpdaterApp.ensureRunning(done)
  16. })
  17. beforeEach(async function () {
  18. await rclient.flushall()
  19. })
  20. async function runScript(options) {
  21. let result
  22. try {
  23. result = await promisify(exec)(
  24. Object.entries(options)
  25. .map(([key, value]) => `${key}=${value}`)
  26. .concat(['node', 'scripts/check_redis_mongo_sync_state.js'])
  27. .join(' ')
  28. )
  29. } catch (error) {
  30. // includes details like exit code, stdErr and stdOut
  31. return error
  32. }
  33. result.code = 0
  34. return result
  35. }
  36. describe('without projects', function () {
  37. it('should work when in sync', async function () {
  38. const result = await runScript({})
  39. expect(result.code).to.equal(0)
  40. expect(result.stdout).to.include('Processed 0 projects')
  41. expect(result.stdout).to.include(
  42. 'Found 0 projects with 0 out of sync docs'
  43. )
  44. })
  45. })
  46. describe('with a project', function () {
  47. let projectId, docId
  48. beforeEach(function (done) {
  49. projectId = DocUpdaterClient.randomId()
  50. docId = DocUpdaterClient.randomId()
  51. MockWebApi.insertDoc(projectId, docId, {
  52. lines: ['mongo', 'lines'],
  53. version: 1,
  54. })
  55. DocUpdaterClient.getDoc(projectId, docId, done)
  56. })
  57. it('should work when in sync', async function () {
  58. const result = await runScript({})
  59. expect(result.code).to.equal(0)
  60. expect(result.stdout).to.include('Processed 1 projects')
  61. expect(result.stdout).to.include(
  62. 'Found 0 projects with 0 out of sync docs'
  63. )
  64. })
  65. describe('with out of sync lines', function () {
  66. beforeEach(function () {
  67. MockWebApi.insertDoc(projectId, docId, {
  68. lines: ['updated', 'mongo', 'lines'],
  69. version: 1,
  70. })
  71. })
  72. it('should detect the out of sync state', async function () {
  73. const result = await runScript({})
  74. expect(result.code).to.equal(1)
  75. expect(result.stdout).to.include('Processed 1 projects')
  76. expect(result.stdout).to.include(
  77. 'Found 1 projects with 1 out of sync docs'
  78. )
  79. })
  80. })
  81. describe('with out of sync ranges', function () {
  82. beforeEach(function () {
  83. MockWebApi.insertDoc(projectId, docId, {
  84. lines: ['mongo', 'lines'],
  85. version: 1,
  86. ranges: { changes: ['FAKE CHANGE'] },
  87. })
  88. })
  89. it('should detect the out of sync state', async function () {
  90. const result = await runScript({})
  91. expect(result.code).to.equal(1)
  92. expect(result.stdout).to.include('Processed 1 projects')
  93. expect(result.stdout).to.include(
  94. 'Found 1 projects with 1 out of sync docs'
  95. )
  96. })
  97. })
  98. describe('with out of sync version', function () {
  99. beforeEach(function () {
  100. MockWebApi.insertDoc(projectId, docId, {
  101. lines: ['mongo', 'lines'],
  102. version: 2,
  103. })
  104. })
  105. it('should detect the out of sync state', async function () {
  106. const result = await runScript({})
  107. expect(result.code).to.equal(1)
  108. expect(result.stdout).to.include('Processed 1 projects')
  109. expect(result.stdout).to.include(
  110. 'Found 1 projects with 1 out of sync docs'
  111. )
  112. })
  113. it('should auto-fix the out of sync state', async function () {
  114. const result = await runScript({
  115. AUTO_FIX_VERSION_MISMATCH: 'true',
  116. })
  117. expect(result.code).to.equal(0)
  118. expect(result.stdout).to.include('Processed 1 projects')
  119. expect(result.stdout).to.include(
  120. 'Found 0 projects with 0 out of sync docs'
  121. )
  122. })
  123. })
  124. describe('with a project', function () {
  125. let projectId2, docId2
  126. beforeEach(function (done) {
  127. projectId2 = DocUpdaterClient.randomId()
  128. docId2 = DocUpdaterClient.randomId()
  129. MockWebApi.insertDoc(projectId2, docId2, {
  130. lines: ['mongo', 'lines'],
  131. version: 1,
  132. })
  133. DocUpdaterClient.getDoc(projectId2, docId2, done)
  134. })
  135. it('should work when in sync', async function () {
  136. const result = await runScript({})
  137. expect(result.code).to.equal(0)
  138. expect(result.stdout).to.include('Processed 2 projects')
  139. expect(result.stdout).to.include(
  140. 'Found 0 projects with 0 out of sync docs'
  141. )
  142. })
  143. describe('with one out of sync', function () {
  144. beforeEach(function () {
  145. MockWebApi.insertDoc(projectId, docId, {
  146. lines: ['updated', 'mongo', 'lines'],
  147. version: 1,
  148. })
  149. })
  150. it('should detect one project out of sync', async function () {
  151. const result = await runScript({})
  152. expect(result.code).to.equal(1)
  153. expect(result.stdout).to.include('Processed 2 projects')
  154. expect(result.stdout).to.include(
  155. 'Found 1 projects with 1 out of sync docs'
  156. )
  157. })
  158. it('should write differences to disk', async function () {
  159. const FOLDER = '/tmp/folder'
  160. await fs.promises.rm(FOLDER, { recursive: true, force: true })
  161. const result = await runScript({
  162. WRITE_CONTENT: 'true',
  163. FOLDER,
  164. })
  165. expect(result.code).to.equal(1)
  166. expect(result.stdout).to.include('Processed 2 projects')
  167. expect(result.stdout).to.include(
  168. 'Found 1 projects with 1 out of sync docs'
  169. )
  170. const dir = Path.join(FOLDER, projectId, docId)
  171. expect(await fs.promises.readdir(FOLDER)).to.deep.equal([projectId])
  172. expect(await fs.promises.readdir(dir)).to.deep.equal([
  173. 'mongo-snapshot.txt',
  174. 'redis-snapshot.txt',
  175. ])
  176. expect(
  177. await fs.promises.readFile(
  178. Path.join(dir, 'mongo-snapshot.txt'),
  179. 'utf-8'
  180. )
  181. ).to.equal('updated\nmongo\nlines')
  182. expect(
  183. await fs.promises.readFile(
  184. Path.join(dir, 'redis-snapshot.txt'),
  185. 'utf-8'
  186. )
  187. ).to.equal('mongo\nlines')
  188. })
  189. })
  190. describe('with both out of sync', function () {
  191. beforeEach(function () {
  192. MockWebApi.insertDoc(projectId, docId, {
  193. lines: ['updated', 'mongo', 'lines'],
  194. version: 1,
  195. })
  196. MockWebApi.insertDoc(projectId2, docId2, {
  197. lines: ['updated2', 'mongo', 'lines'],
  198. version: 1,
  199. })
  200. })
  201. it('should detect both projects out of sync', async function () {
  202. const result = await runScript({})
  203. expect(result.code).to.equal(1)
  204. expect(result.stdout).to.include('Processed 2 projects')
  205. expect(result.stdout).to.include(
  206. 'Found 2 projects with 2 out of sync docs'
  207. )
  208. })
  209. })
  210. })
  211. })
  212. describe('with more projects than the LIMIT', function () {
  213. for (let i = 0; i < 20; i++) {
  214. beforeEach(function (done) {
  215. const projectId = DocUpdaterClient.randomId()
  216. const docId = DocUpdaterClient.randomId()
  217. MockWebApi.insertDoc(projectId, docId, {
  218. lines: ['mongo', 'lines'],
  219. version: 1,
  220. })
  221. DocUpdaterClient.getDoc(projectId, docId, done)
  222. })
  223. }
  224. it('should flag limit', async function () {
  225. const result = await runScript({ LIMIT: '4' })
  226. expect(result.code).to.equal(2)
  227. // A redis SCAN may return more than COUNT (aka LIMIT) entries. Match loosely.
  228. expect(result.stdout).to.match(/Processed \d+ projects/)
  229. expect(result.stderr).to.include(
  230. 'Found too many un-flushed projects (LIMIT=4). Please fix the reported projects first, then try again.'
  231. )
  232. })
  233. it('should continue with auto-flush', async function () {
  234. const result = await runScript({
  235. LIMIT: '4',
  236. FLUSH_IN_SYNC_PROJECTS: 'true',
  237. })
  238. expect(result.code).to.equal(0)
  239. expect(result.stdout).to.include('Processed 20 projects')
  240. })
  241. })
  242. })