TemplatesManager.test.mjs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import { beforeEach, describe, it, vi } from 'vitest'
  2. import sinon from 'sinon'
  3. import { RequestFailedError } from '@overleaf/fetch-utils'
  4. import { ReadableString } from '@overleaf/stream-utils'
  5. const modulePath = '../../../../app/src/Features/Templates/TemplatesManager'
  6. describe('TemplatesManager', function () {
  7. beforeEach(async function (ctx) {
  8. ctx.project_id = 'project-id'
  9. ctx.brandVariationId = 'brand-variation-id'
  10. ctx.compiler = 'pdflatex'
  11. ctx.imageName = 'TL2017'
  12. ctx.mainFile = 'main.tex'
  13. ctx.templateId = 'template-id'
  14. ctx.templateName = 'template name'
  15. ctx.templateVersionId = 'template-version-id'
  16. ctx.user_id = 'user-id'
  17. ctx.dumpFolder = 'dump/path'
  18. ctx.uuid = '1234'
  19. ctx.dumpPath = `${ctx.dumpFolder}/${ctx.uuid}_templates-manager`
  20. ctx.callback = sinon.stub()
  21. ctx.pipeline = sinon.stub().callsFake(async (stream, res) => {
  22. if (res.callback) res.callback()
  23. })
  24. ctx.request = sinon.stub().returns({
  25. pipe() {},
  26. on() {},
  27. response: {
  28. statusCode: 200,
  29. },
  30. })
  31. ctx.fs = {
  32. promises: { unlink: sinon.stub() },
  33. unlink: sinon.stub(),
  34. createWriteStream: sinon.stub().returns({ on: sinon.stub().yields() }),
  35. }
  36. ctx.ProjectUploadManager = {
  37. promises: {
  38. createProjectFromZipArchiveWithName: sinon.stub().resolves({
  39. project: { _id: ctx.project_id },
  40. fileEntries: [],
  41. docEntries: [],
  42. }),
  43. },
  44. }
  45. ctx.ProjectOptionsHandler = {
  46. promises: {
  47. setCompiler: sinon.stub().resolves(),
  48. setImageName: sinon.stub().resolves(),
  49. setBrandVariationId: sinon.stub().resolves(),
  50. normalizeCompiler: sinon.stub().returnsArg(0),
  51. normalizeImageName: sinon.stub().returnsArg(0),
  52. },
  53. }
  54. ctx.ProjectRootDocManager = {
  55. promises: {
  56. setRootDocFromName: sinon.stub().resolves(),
  57. },
  58. }
  59. ctx.ProjectDetailsHandler = {
  60. getProjectDescription: sinon.stub(),
  61. fixProjectName: sinon.stub().returns(ctx.templateName),
  62. }
  63. ctx.Project = { updateOne: sinon.stub().resolves() }
  64. ctx.mockStream = new ReadableString('{}')
  65. ctx.mockResponse = {
  66. status: 200,
  67. headers: new Headers({
  68. 'Content-Length': '2',
  69. 'Content-Type': 'application/json',
  70. }),
  71. }
  72. ctx.FetchUtils = {
  73. fetchJson: sinon.stub(),
  74. fetchStreamWithResponse: sinon.stub().resolves({
  75. stream: ctx.mockStream,
  76. response: ctx.mockResponse,
  77. }),
  78. RequestFailedError,
  79. }
  80. vi.doMock('@overleaf/fetch-utils', () => ctx.FetchUtils)
  81. vi.doMock(
  82. '../../../../app/src/Features/Uploads/ProjectUploadManager',
  83. () => ({ default: ctx.ProjectUploadManager })
  84. )
  85. vi.doMock(
  86. '../../../../app/src/Features/Project/ProjectOptionsHandler',
  87. () => ({ default: ctx.ProjectOptionsHandler })
  88. )
  89. vi.doMock(
  90. '../../../../app/src/Features/Project/ProjectRootDocManager',
  91. () => ({ default: ctx.ProjectRootDocManager })
  92. )
  93. vi.doMock(
  94. '../../../../app/src/Features/Project/ProjectDetailsHandler',
  95. () => ({ default: ctx.ProjectDetailsHandler })
  96. )
  97. ctx.SessionManager = {
  98. getLoggedInUserId: sinon.stub(),
  99. }
  100. vi.doMock(
  101. '../../../../app/src/Features/Authentication/SessionManager',
  102. () => ({ default: ctx.SessionManager })
  103. )
  104. vi.doMock('@overleaf/settings', () => ({
  105. default: {
  106. path: {
  107. dumpFolder: ctx.dumpFolder,
  108. },
  109. siteUrl: (ctx.siteUrl = 'http://127.0.0.1:3000'),
  110. defaultLatexCompiler: 'pdflatex',
  111. apis: {
  112. v1: {
  113. url: (ctx.v1Url = 'http://overleaf.com'),
  114. user: 'overleaf',
  115. pass: 'password',
  116. timeout: 10,
  117. },
  118. },
  119. overleaf: {
  120. host: ctx.v1Url,
  121. },
  122. },
  123. }))
  124. vi.doMock('node:crypto', () => ({
  125. default: {
  126. randomUUID: () => ctx.uuid,
  127. },
  128. }))
  129. vi.doMock('node:fs', () => ({ default: ctx.fs }))
  130. vi.doMock('request', () => ({ default: ctx.request }))
  131. vi.doMock('../../../../app/src/models/Project', () => ({
  132. Project: ctx.Project,
  133. }))
  134. vi.doMock('node:stream/promises', () => ({ pipeline: ctx.pipeline }))
  135. vi.doMock('../../../../app/src/Features/Compile/ClsiCacheManager', () => ({
  136. default: {
  137. prepareClsiCache: sinon.stub().rejects(new Error('ignore this')),
  138. },
  139. }))
  140. ctx.TemplatesManager = (await import(modulePath)).default.promises
  141. ctx.zipUrl =
  142. '%2Ftemplates%2F52fb86a81ae1e566597a25f6%2Fv%2F4%2Fzip&templateName=Moderncv%20Banking&compiler=pdflatex'
  143. })
  144. describe('createProjectFromV1Template', function () {
  145. describe('when all options passed', function () {
  146. beforeEach(async function (ctx) {
  147. await ctx.TemplatesManager.createProjectFromV1Template(
  148. ctx.brandVariationId,
  149. ctx.compiler,
  150. ctx.mainFile,
  151. ctx.templateId,
  152. ctx.templateName,
  153. ctx.templateVersionId,
  154. ctx.user_id,
  155. ctx.imageName
  156. )
  157. })
  158. it('should fetch zip from v1 based on template id', function (ctx) {
  159. ctx.FetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
  160. `${ctx.v1Url}/api/v1/overleaf/templates/${ctx.templateVersionId}`
  161. )
  162. })
  163. it('should save temporary file', function (ctx) {
  164. ctx.fs.createWriteStream.should.have.been.calledWith(ctx.dumpPath)
  165. })
  166. it('should create project', function (ctx) {
  167. ctx.ProjectUploadManager.promises.createProjectFromZipArchiveWithName.should.have.been.calledWithMatch(
  168. ctx.user_id,
  169. ctx.templateName,
  170. ctx.dumpPath,
  171. {
  172. fromV1TemplateId: ctx.templateId,
  173. fromV1TemplateVersionId: ctx.templateVersionId,
  174. compiler: ctx.compiler,
  175. imageName: ctx.imageName,
  176. brandVariationId: ctx.brandVariationId,
  177. }
  178. )
  179. })
  180. it('should unlink file', function (ctx) {
  181. ctx.fs.promises.unlink.should.have.been.calledWith(ctx.dumpPath)
  182. })
  183. it('should set project rootDoc when passed', function (ctx) {
  184. ctx.ProjectRootDocManager.promises.setRootDocFromName.should.have.been.calledWithMatch(
  185. ctx.project_id,
  186. ctx.mainFile
  187. )
  188. })
  189. })
  190. describe('when some options not set', function () {
  191. beforeEach(async function (ctx) {
  192. await ctx.TemplatesManager.createProjectFromV1Template(
  193. null,
  194. null,
  195. null,
  196. ctx.templateId,
  197. ctx.templateName,
  198. ctx.templateVersionId,
  199. ctx.user_id,
  200. null
  201. )
  202. })
  203. it('should create project', function (ctx) {
  204. ctx.ProjectUploadManager.promises.createProjectFromZipArchiveWithName.should.have.been.calledWithMatch(
  205. ctx.user_id,
  206. ctx.templateName,
  207. ctx.dumpPath,
  208. {
  209. fromV1TemplateId: ctx.templateId,
  210. fromV1TemplateVersionId: ctx.templateVersionId,
  211. compiler: 'pdflatex',
  212. imageName: 'wl_texlive:2018.1',
  213. }
  214. )
  215. })
  216. it('should not set missing project options', function (ctx) {
  217. ctx.ProjectRootDocManager.promises.setRootDocFromName.called.should.equal(
  218. false
  219. )
  220. })
  221. })
  222. })
  223. })