LinkedFilesController.test.mjs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import { expect, vi } from 'vitest'
  2. import sinon from 'sinon'
  3. const modulePath =
  4. '../../../../app/src/Features/LinkedFiles/LinkedFilesController.mjs'
  5. describe('LinkedFilesController', function () {
  6. beforeEach(function (ctx) {
  7. ctx.fakeTime = new Date()
  8. ctx.clock = sinon.useFakeTimers(ctx.fakeTime.getTime())
  9. })
  10. afterEach(function (ctx) {
  11. ctx.clock.restore()
  12. })
  13. beforeEach(async function (ctx) {
  14. ctx.userId = 'user-id'
  15. ctx.Agent = {
  16. promises: {
  17. createLinkedFile: sinon.stub().resolves(),
  18. refreshLinkedFile: sinon.stub().resolves(),
  19. },
  20. }
  21. ctx.projectId = 'projectId'
  22. ctx.provider = 'provider'
  23. ctx.fileName = 'linked-file-name'
  24. ctx.data = { customAgentData: 'foo' }
  25. ctx.LinkedFilesHandler = {
  26. promises: {
  27. getFileById: sinon.stub(),
  28. },
  29. }
  30. ctx.AnalyticsManager = {}
  31. ctx.SessionManager = {
  32. getLoggedInUserId: sinon.stub().returns(ctx.userId),
  33. }
  34. ctx.EditorRealTimeController = { emitToRoom: sinon.stub() }
  35. ctx.UrlAgent = {}
  36. ctx.ProjectFileAgent = {}
  37. ctx.ProjectOutputFileAgent = {}
  38. ctx.EditorController = {}
  39. ctx.ProjectLocator = {}
  40. ctx.logger = {
  41. error: sinon.stub(),
  42. }
  43. ctx.settings = { enabledLinkedFileTypes: [] }
  44. vi.doMock(
  45. '../../../../app/src/Features/Authentication/SessionManager',
  46. () => ({
  47. default: ctx.SessionManager,
  48. })
  49. )
  50. vi.doMock(
  51. '../../../../app/src/Features/Analytics/AnalyticsManager',
  52. () => ({
  53. default: ctx.AnalyticsManager,
  54. })
  55. )
  56. vi.doMock(
  57. '../../../../app/src/Features/LinkedFiles/LinkedFilesHandler',
  58. () => ({
  59. default: ctx.LinkedFilesHandler,
  60. })
  61. )
  62. vi.doMock(
  63. '../../../../app/src/Features/Editor/EditorRealTimeController',
  64. () => ({
  65. default: ctx.EditorRealTimeController,
  66. })
  67. )
  68. vi.doMock('../../../../app/src/Features/LinkedFiles/UrlAgent', () => ({
  69. default: ctx.UrlAgent,
  70. }))
  71. vi.doMock(
  72. '../../../../app/src/Features/LinkedFiles/ProjectFileAgent',
  73. () => ({
  74. default: ctx.ProjectFileAgent,
  75. })
  76. )
  77. vi.doMock(
  78. '../../../../app/src/Features/LinkedFiles/ProjectOutputFileAgent',
  79. () => ({
  80. default: ctx.ProjectOutputFileAgent,
  81. })
  82. )
  83. vi.doMock('../../../../app/src/Features/Editor/EditorController', () => ({
  84. default: ctx.EditorController,
  85. }))
  86. vi.doMock('../../../../app/src/Features/Project/ProjectLocator', () => ({
  87. default: ctx.ProjectLocator,
  88. }))
  89. vi.doMock('@overleaf/logger', () => ({
  90. default: ctx.logger,
  91. }))
  92. vi.doMock('@overleaf/settings', () => ({
  93. default: ctx.settings,
  94. }))
  95. ctx.LinkedFilesController = (await import(modulePath)).default
  96. ctx.LinkedFilesController._getAgent = sinon.stub().resolves(ctx.Agent)
  97. })
  98. describe('createLinkedFile', function () {
  99. beforeEach(function (ctx) {
  100. ctx.req = {
  101. params: { project_id: ctx.projectId },
  102. body: {
  103. name: ctx.fileName,
  104. provider: ctx.provider,
  105. data: ctx.data,
  106. },
  107. }
  108. ctx.next = sinon.stub()
  109. })
  110. it('sets importedAt timestamp on linkedFileData', async function (ctx) {
  111. await new Promise(resolve => {
  112. ctx.next = sinon.stub().callsFake(() => resolve('unexpected error'))
  113. ctx.res = {
  114. json: () => {
  115. expect(ctx.Agent.promises.createLinkedFile).to.have.been.calledWith(
  116. ctx.projectId,
  117. { ...ctx.data, importedAt: ctx.fakeTime.toISOString() },
  118. ctx.fileName,
  119. undefined,
  120. ctx.userId
  121. )
  122. resolve()
  123. },
  124. }
  125. ctx.LinkedFilesController.createLinkedFile(ctx.req, ctx.res, ctx.next)
  126. })
  127. })
  128. })
  129. describe('refreshLinkedFiles', function () {
  130. beforeEach(function (ctx) {
  131. ctx.data.provider = ctx.provider
  132. ctx.file = {
  133. name: ctx.fileName,
  134. linkedFileData: {
  135. ...ctx.data,
  136. importedAt: new Date(2020, 1, 1).toISOString(),
  137. },
  138. }
  139. ctx.LinkedFilesHandler.promises.getFileById
  140. .withArgs(ctx.projectId, 'file-id')
  141. .resolves({
  142. file: ctx.file,
  143. path: 'fake-path',
  144. parentFolder: {
  145. _id: 'parent-folder-id',
  146. },
  147. })
  148. ctx.req = {
  149. params: { project_id: ctx.projectId, file_id: 'file-id' },
  150. body: {},
  151. }
  152. ctx.next = sinon.stub()
  153. })
  154. it('resets importedAt timestamp on linkedFileData', async function (ctx) {
  155. await new Promise(resolve => {
  156. ctx.next = sinon.stub().callsFake(() => resolve('unexpected error'))
  157. ctx.res = {
  158. json: () => {
  159. expect(
  160. ctx.Agent.promises.refreshLinkedFile
  161. ).to.have.been.calledWith(
  162. ctx.projectId,
  163. {
  164. ...ctx.data,
  165. importedAt: ctx.fakeTime.toISOString(),
  166. },
  167. ctx.name,
  168. 'parent-folder-id',
  169. ctx.userId
  170. )
  171. resolve()
  172. },
  173. }
  174. ctx.LinkedFilesController.refreshLinkedFile(ctx.req, ctx.res, ctx.next)
  175. })
  176. })
  177. describe('when bib file re-indexing is required', function () {
  178. const clientId = 'client-id'
  179. beforeEach(function (ctx) {
  180. ctx.req.body.shouldReindexReferences = true
  181. ctx.req.body.clientId = clientId
  182. })
  183. it('informs clients to re-index bib references', async function (ctx) {
  184. await new Promise(resolve => {
  185. ctx.next = sinon.stub().callsFake(() => resolve('unexpected error'))
  186. ctx.res = {
  187. json: () => {
  188. expect(
  189. ctx.EditorRealTimeController.emitToRoom
  190. ).to.have.been.calledWith(
  191. ctx.projectId,
  192. 'references:keys:updated',
  193. [],
  194. true,
  195. clientId
  196. )
  197. resolve()
  198. },
  199. }
  200. ctx.LinkedFilesController.refreshLinkedFile(
  201. ctx.req,
  202. ctx.res,
  203. ctx.next
  204. )
  205. })
  206. })
  207. })
  208. })
  209. })