upgrading.spec.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import { ensureUserExists, login } from './helpers/login'
  2. import { isExcludedBySharding, startWith } from './helpers/config'
  3. import { dockerCompose, runScript } from './helpers/hostAdminClient'
  4. import { createProject, openProjectByName } from './helpers/project'
  5. import { throttledRecompile } from './helpers/compile'
  6. import { v4 as uuid } from 'uuid'
  7. const USER = 'user@example.com'
  8. const PROJECT_NAME = 'Old Project'
  9. describe('Upgrading', function () {
  10. if (isExcludedBySharding('PRO_CUSTOM_3')) return
  11. function testUpgrade(
  12. steps: {
  13. version: string
  14. vars?: Object
  15. newProjectButtonMatcher?: RegExp
  16. hook?: () => void
  17. }[]
  18. ) {
  19. const startOptions = steps.shift()!
  20. before(async () => {
  21. cy.log('Create old instance')
  22. })
  23. startWith({
  24. pro: true,
  25. version: startOptions.version,
  26. withDataDir: true,
  27. resetData: true,
  28. vars: startOptions.vars,
  29. })
  30. before(function () {
  31. cy.log('Create initial user after deleting it')
  32. })
  33. ensureUserExists({ email: USER })
  34. before(() => {
  35. cy.log('Populate old instance')
  36. login(USER)
  37. createProject(PROJECT_NAME, {
  38. newProjectButtonMatcher: startOptions.newProjectButtonMatcher,
  39. })
  40. const recompile = throttledRecompile()
  41. cy.log('Wait for successful compile')
  42. cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME)
  43. cy.log('Increment the doc version three times')
  44. for (let i = 0; i < 3; i++) {
  45. cy.log('Add content')
  46. cy.findByText('\\maketitle').parent().click()
  47. cy.findByText('\\maketitle')
  48. .parent()
  49. .type(`\n\\section{{}Old Section ${i}}`)
  50. cy.log('Trigger full flush')
  51. recompile()
  52. cy.findByRole('navigation', {
  53. name: 'Project Layout, Sharing, and Submission',
  54. })
  55. .findByRole('button', { name: 'Menu' })
  56. .click()
  57. cy.findByText('Source').click()
  58. cy.get('.left-menu-modal-backdrop').click({ force: true })
  59. }
  60. cy.log('Check compile and history')
  61. for (let i = 0; i < 3; i++) {
  62. cy.get('.pdf-viewer').should('contain.text', `Old Section ${i}`)
  63. }
  64. cy.findByText('History').click()
  65. for (let i = 0; i < 3; i++) {
  66. cy.findByText(new RegExp(`\\\\section\{Old Section ${i}}`))
  67. }
  68. })
  69. for (const step of steps) {
  70. before(() => {
  71. cy.log(`Upgrade to version ${step.version}`)
  72. // Navigate way from editor to avoid redirect to /login when the next instance comes up (which slows down tests)
  73. cy.visit('/project', {})
  74. })
  75. before(async function () {
  76. cy.log('Graceful shutdown: flush all the things')
  77. this.timeout(20 * 1000)
  78. // Ideally we could use the container shutdown procedure, but it's too slow and unreliable for tests.
  79. // TODO(das7pad): adopt the below after speeding up the graceful shutdown procedure on all supported releases
  80. // await dockerCompose('stop', 'sharelatex')
  81. // For now, we are stuck with manually flushing things
  82. await runScript({
  83. cwd: 'services/document-updater',
  84. script: 'scripts/flush_all.js',
  85. })
  86. await runScript({
  87. cwd: 'services/project-history',
  88. script: 'scripts/flush_all.js',
  89. })
  90. })
  91. startWith({
  92. pro: true,
  93. version: step.version,
  94. vars: step.vars,
  95. withDataDir: true,
  96. })
  97. step.hook?.()
  98. }
  99. beforeEach(() => {
  100. login(USER)
  101. })
  102. it('should list the old project', () => {
  103. cy.visit('/project')
  104. cy.findByText(PROJECT_NAME)
  105. })
  106. it('should open the old project', () => {
  107. openProjectByName(PROJECT_NAME)
  108. cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
  109. cy.findByRole('navigation', {
  110. name: 'Project actions',
  111. }).within(() => {
  112. cy.findByText(PROJECT_NAME)
  113. })
  114. const recompile = throttledRecompile()
  115. cy.log('wait for successful compile')
  116. cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME)
  117. cy.get('.pdf-viewer').should('contain.text', 'Old Section 2')
  118. cy.log('Add more content')
  119. const newSection = `New Section ${uuid()}`
  120. cy.findByText('\\maketitle').parent().click()
  121. cy.findByText('\\maketitle').parent().type(`\n\\section{{}${newSection}}`)
  122. cy.log('Check compile and history')
  123. recompile()
  124. cy.get('.pdf-viewer').should('contain.text', newSection)
  125. cy.findByText('History').click()
  126. cy.findByText(/\\section\{Old Section 2}/)
  127. cy.findByText(new RegExp(`\\\\section\\{${newSection}}`))
  128. })
  129. }
  130. const optionsFourDotTwo = {
  131. version: '4.2',
  132. vars: {
  133. // Add core vars with old branding
  134. SHARELATEX_SITE_URL: 'http://sharelatex',
  135. SHARELATEX_MONGO_URL: 'mongodb://mongo/sharelatex',
  136. SHARELATEX_REDIS_HOST: 'redis',
  137. },
  138. newProjectButtonMatcher: /create first project/i,
  139. }
  140. const optionsBinaryFilesMigration = {
  141. version: '5.5.5',
  142. hook() {
  143. before(async function () {
  144. await runScript({
  145. cwd: 'services/history-v1',
  146. script: 'storage/scripts/back_fill_file_hash.mjs',
  147. args: ['--all'],
  148. })
  149. })
  150. },
  151. }
  152. describe('from 4.2 to latest', () => {
  153. testUpgrade([
  154. optionsFourDotTwo,
  155. optionsBinaryFilesMigration,
  156. { version: 'latest' },
  157. ])
  158. })
  159. describe('from 5.0 to latest', () => {
  160. testUpgrade([
  161. { version: '5.0' },
  162. optionsBinaryFilesMigration,
  163. { version: 'latest' },
  164. ])
  165. })
  166. describe('doc version recovery', () => {
  167. testUpgrade([
  168. optionsFourDotTwo,
  169. {
  170. version: '5.0.1-RC1',
  171. hook() {
  172. before(function () {
  173. login(USER)
  174. cy.visit('/')
  175. cy.findByText(PROJECT_NAME).click()
  176. const recompile = throttledRecompile()
  177. cy.log('Make a change')
  178. cy.findByText('\\maketitle').parent().click()
  179. cy.findByText('\\maketitle')
  180. .parent()
  181. .type('\n\\section{{}FiveOOne Section}')
  182. cy.log('Trigger flush')
  183. recompile()
  184. cy.get('.pdf-viewer').should('contain.text', 'FiveOOne Section')
  185. cy.log('Check for broken history, i.e. not synced with latest edit')
  186. cy.findByText('History').click()
  187. cy.findByText(/\\section\{Old Section 2}/) // wait for lazy loading
  188. cy.findByText(/\\section\{FiveOOne Section}/).should('not.exist')
  189. })
  190. },
  191. },
  192. optionsBinaryFilesMigration,
  193. {
  194. version: 'latest',
  195. hook() {
  196. before(async function () {
  197. this.timeout(20_000)
  198. const needle = 'Finished resyncing history for all projects.'
  199. for (let i = 0; i < 30; i++) {
  200. const { stdout } = await dockerCompose('logs', 'sharelatex')
  201. if (stdout.includes(needle)) {
  202. return
  203. }
  204. await new Promise(resolve => setTimeout(resolve, 500))
  205. }
  206. const { stdout } = await dockerCompose('logs', 'sharelatex')
  207. expect(stdout).to.contain(
  208. needle,
  209. 'Doc version recovery did not finish yet.'
  210. )
  211. })
  212. before(function () {
  213. login(USER)
  214. cy.visit('/')
  215. cy.findByText(PROJECT_NAME).click()
  216. cy.log(
  217. 'The edit that was made while the history was broken should be there now.'
  218. )
  219. cy.findByText('History').click()
  220. cy.findByText(/\\section\{FiveOOne Section}/)
  221. // TODO(das7pad): restore after https://github.com/overleaf/internal/issues/19588 is fixed.
  222. // cy.log('Check indicator of force resync')
  223. // cy.findByText('Overleaf History System')
  224. })
  225. },
  226. },
  227. ])
  228. })
  229. })