UrlCache.test.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { vi, expect, describe, beforeEach, it } from 'vitest'
  2. import sinon from 'sinon'
  3. import path from 'node:path'
  4. const modulePath = path.join(import.meta.dirname, '../../../app/js/UrlCache')
  5. describe('UrlCache', () => {
  6. beforeEach(async ctx => {
  7. ctx.callback = sinon.stub()
  8. ctx.url =
  9. 'http://filestore/project/60b0dd39c418bc00598a0d22/file/60ae721ffb1d920027d3201f'
  10. ctx.fallbackURL = 'http://filestore/bucket/project-blobs/key/ab/cd/ef'
  11. ctx.project_id = '60b0dd39c418bc00598a0d22'
  12. vi.doMock('../../../app/js/UrlFetcher', () => ({
  13. default: (ctx.UrlFetcher = {
  14. promises: { pipeUrlToFileWithRetry: sinon.stub().resolves() },
  15. }),
  16. }))
  17. vi.doMock('@overleaf/settings', () => ({
  18. default: (ctx.Settings = {
  19. path: { clsiCacheDir: '/cache/dir' },
  20. }),
  21. }))
  22. vi.doMock('@overleaf/metrics', () => ({
  23. default: {
  24. Timer: sinon.stub().returns({ done: sinon.stub() }),
  25. },
  26. }))
  27. ctx.fs = {
  28. promises: {
  29. rm: sinon.stub().resolves(),
  30. copyFile: sinon.stub().resolves(),
  31. },
  32. }
  33. vi.doMock('fs', () => ({ default: ctx.fs }))
  34. return (ctx.UrlCache = (await import(modulePath)).default)
  35. })
  36. describe('downloadUrlToFile', () => {
  37. beforeEach(ctx => {
  38. ctx.destPath = 'path/to/destination'
  39. })
  40. it('should not download on the happy path', async ctx => {
  41. await new Promise((resolve, reject) => {
  42. ctx.UrlCache.downloadUrlToFile(
  43. ctx.project_id,
  44. ctx.url,
  45. ctx.fallbackURL,
  46. ctx.destPath,
  47. ctx.lastModified,
  48. error => {
  49. expect(error).to.not.exist
  50. expect(
  51. ctx.UrlFetcher.promises.pipeUrlToFileWithRetry.called
  52. ).to.equal(false)
  53. resolve()
  54. }
  55. )
  56. })
  57. })
  58. it('should not download on the semi-happy path', async ctx => {
  59. await new Promise((resolve, reject) => {
  60. const codedError = new Error()
  61. codedError.code = 'ENOENT'
  62. ctx.fs.promises.copyFile.onCall(0).rejects(codedError)
  63. ctx.fs.promises.copyFile.onCall(1).resolves()
  64. ctx.UrlCache.downloadUrlToFile(
  65. ctx.project_id,
  66. ctx.url,
  67. ctx.fallbackURL,
  68. ctx.destPath,
  69. ctx.lastModified,
  70. error => {
  71. expect(error).to.not.exist
  72. expect(
  73. ctx.UrlFetcher.promises.pipeUrlToFileWithRetry.called
  74. ).to.equal(false)
  75. resolve()
  76. }
  77. )
  78. })
  79. })
  80. it('should download on cache miss', async ctx => {
  81. await new Promise((resolve, reject) => {
  82. const codedError = new Error()
  83. codedError.code = 'ENOENT'
  84. ctx.fs.promises.copyFile.onCall(0).rejects(codedError)
  85. ctx.fs.promises.copyFile.onCall(1).rejects(codedError)
  86. ctx.fs.promises.copyFile.onCall(2).resolves()
  87. ctx.UrlCache.downloadUrlToFile(
  88. ctx.project_id,
  89. ctx.url,
  90. ctx.fallbackURL,
  91. ctx.destPath,
  92. ctx.lastModified,
  93. error => {
  94. expect(error).to.not.exist
  95. expect(
  96. ctx.UrlFetcher.promises.pipeUrlToFileWithRetry.called
  97. ).to.equal(true)
  98. resolve()
  99. }
  100. )
  101. })
  102. })
  103. it('should raise non cache-miss errors', async ctx => {
  104. await new Promise((resolve, reject) => {
  105. const codedError = new Error()
  106. codedError.code = 'FOO'
  107. ctx.fs.promises.copyFile.rejects(codedError)
  108. ctx.UrlCache.downloadUrlToFile(
  109. ctx.project_id,
  110. ctx.url,
  111. ctx.fallbackURL,
  112. ctx.destPath,
  113. ctx.lastModified,
  114. error => {
  115. expect(error).to.equal(codedError)
  116. resolve()
  117. }
  118. )
  119. })
  120. })
  121. })
  122. describe('clearProject', () => {
  123. beforeEach(async ctx => {
  124. await ctx.UrlCache.promises.clearProject(ctx.project_id)
  125. })
  126. it('should clear the cache in bulk', ctx => {
  127. expect(
  128. ctx.fs.promises.rm.calledWith('/cache/dir/' + ctx.project_id, {
  129. force: true,
  130. recursive: true,
  131. })
  132. ).to.equal(true)
  133. })
  134. })
  135. })