upgrading.spec.ts 6.7 KB

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