DocumentControllerTests.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. const sinon = require('sinon')
  2. const modulePath =
  3. '../../../../app/src/Features/Documents/DocumentController.js'
  4. const SandboxedModule = require('sandboxed-module')
  5. const MockRequest = require('../helpers/MockRequest')
  6. const MockResponse = require('../helpers/MockResponse')
  7. const Errors = require('../../../../app/src/Features/Errors/Errors')
  8. describe('DocumentController', function () {
  9. beforeEach(function () {
  10. this.DocumentController = SandboxedModule.require(modulePath, {
  11. requires: {
  12. '../Project/ProjectGetter': (this.ProjectGetter = {}),
  13. '../Project/ProjectLocator': (this.ProjectLocator = {}),
  14. '../Project/ProjectEntityHandler': (this.ProjectEntityHandler = {}),
  15. '../Project/ProjectEntityUpdateHandler':
  16. (this.ProjectEntityUpdateHandler = {}),
  17. },
  18. })
  19. this.res = new MockResponse()
  20. this.req = new MockRequest()
  21. this.next = sinon.stub()
  22. this.project_id = 'project-id-123'
  23. this.doc_id = 'doc-id-123'
  24. this.doc_lines = ['one', 'two', 'three']
  25. this.version = 42
  26. this.ranges = { mock: 'ranges' }
  27. this.pathname = '/a/b/c/file.tex'
  28. this.lastUpdatedAt = new Date().getTime()
  29. this.lastUpdatedBy = 'fake-last-updater-id'
  30. this.rev = 5
  31. })
  32. describe('getDocument', function () {
  33. beforeEach(function () {
  34. this.req.params = {
  35. Project_id: this.project_id,
  36. doc_id: this.doc_id,
  37. }
  38. })
  39. describe('when the project exists without project history enabled', function () {
  40. beforeEach(function () {
  41. this.project = { _id: this.project_id }
  42. this.ProjectGetter.getProject = sinon
  43. .stub()
  44. .callsArgWith(2, null, this.project)
  45. })
  46. describe('when the document exists', function () {
  47. beforeEach(function () {
  48. this.doc = { _id: this.doc_id }
  49. this.ProjectLocator.findElement = sinon
  50. .stub()
  51. .callsArgWith(1, null, this.doc, { fileSystem: this.pathname })
  52. this.ProjectEntityHandler.getDoc = sinon
  53. .stub()
  54. .yields(null, this.doc_lines, this.rev, this.version, this.ranges)
  55. this.DocumentController.getDocument(this.req, this.res, this.next)
  56. })
  57. it('should get the project', function () {
  58. this.ProjectGetter.getProject
  59. .calledWith(this.project_id, { rootFolder: true, overleaf: true })
  60. .should.equal(true)
  61. })
  62. it('should get the pathname of the document', function () {
  63. this.ProjectLocator.findElement
  64. .calledWith({
  65. project: this.project,
  66. element_id: this.doc_id,
  67. type: 'doc',
  68. })
  69. .should.equal(true)
  70. })
  71. it('should get the document content', function () {
  72. this.ProjectEntityHandler.getDoc
  73. .calledWith(this.project_id, this.doc_id)
  74. .should.equal(true)
  75. })
  76. it('should return the document data to the client as JSON', function () {
  77. this.res.type.should.equal('application/json')
  78. this.res.body.should.equal(
  79. JSON.stringify({
  80. lines: this.doc_lines,
  81. version: this.version,
  82. ranges: this.ranges,
  83. pathname: this.pathname,
  84. })
  85. )
  86. })
  87. })
  88. describe("when the document doesn't exist", function () {
  89. beforeEach(function () {
  90. this.ProjectLocator.findElement = sinon
  91. .stub()
  92. .callsArgWith(1, new Errors.NotFoundError('not found'))
  93. this.DocumentController.getDocument(this.req, this.res, this.next)
  94. })
  95. it('should call next with the NotFoundError', function () {
  96. this.next
  97. .calledWith(sinon.match.instanceOf(Errors.NotFoundError))
  98. .should.equal(true)
  99. })
  100. })
  101. })
  102. describe('when project exists with project history enabled', function () {
  103. beforeEach(function () {
  104. this.doc = { _id: this.doc_id }
  105. this.projectHistoryId = 1234
  106. this.projectHistoryDisplay = true
  107. this.projectHistoryType = 'project-history'
  108. this.project = {
  109. _id: this.project_id,
  110. overleaf: {
  111. history: {
  112. id: this.projectHistoryId,
  113. display: this.projectHistoryDisplay,
  114. },
  115. },
  116. }
  117. this.ProjectGetter.getProject = sinon
  118. .stub()
  119. .callsArgWith(2, null, this.project)
  120. this.ProjectLocator.findElement = sinon
  121. .stub()
  122. .callsArgWith(1, null, this.doc, { fileSystem: this.pathname })
  123. this.ProjectEntityHandler.getDoc = sinon
  124. .stub()
  125. .yields(null, this.doc_lines, this.rev, this.version, this.ranges)
  126. this.DocumentController.getDocument(this.req, this.res, this.next)
  127. })
  128. it('should return the history id and display setting to the client as JSON', function () {
  129. this.res.type.should.equal('application/json')
  130. this.res.body.should.equal(
  131. JSON.stringify({
  132. lines: this.doc_lines,
  133. version: this.version,
  134. ranges: this.ranges,
  135. pathname: this.pathname,
  136. projectHistoryId: this.projectHistoryId,
  137. projectHistoryType: this.projectHistoryType,
  138. })
  139. )
  140. })
  141. })
  142. describe('when project exists that was migrated with downgrades allowed', function () {
  143. beforeEach(function () {
  144. this.doc = { _id: this.doc_id }
  145. this.projectHistoryId = 1234
  146. this.projectHistoryDisplay = true
  147. this.projectHistoryType = undefined
  148. this.project = {
  149. _id: this.project_id,
  150. overleaf: {
  151. history: {
  152. id: this.projectHistoryId,
  153. display: this.projectHistoryDisplay,
  154. allowDowngrade: true,
  155. },
  156. },
  157. }
  158. this.ProjectGetter.getProject = sinon
  159. .stub()
  160. .callsArgWith(2, null, this.project)
  161. this.ProjectLocator.findElement = sinon
  162. .stub()
  163. .callsArgWith(1, null, this.doc, { fileSystem: this.pathname })
  164. this.ProjectEntityHandler.getDoc = sinon
  165. .stub()
  166. .yields(null, this.doc_lines, this.rev, this.version, this.ranges)
  167. return this.DocumentController.getDocument(
  168. this.req,
  169. this.res,
  170. this.next
  171. )
  172. })
  173. it('should return the history id in the JSON but not history type, sending history to both services', function () {
  174. this.res.type.should.equal('application/json')
  175. return this.res.body.should.equal(
  176. JSON.stringify({
  177. lines: this.doc_lines,
  178. version: this.version,
  179. ranges: this.ranges,
  180. pathname: this.pathname,
  181. projectHistoryId: this.projectHistoryId,
  182. projectHistoryType: this.projectHistoryType,
  183. })
  184. )
  185. })
  186. })
  187. describe('when the project does not exist', function () {
  188. beforeEach(function () {
  189. this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
  190. this.DocumentController.getDocument(this.req, this.res, this.next)
  191. })
  192. it('returns a 404', function () {
  193. this.res.statusCode.should.equal(404)
  194. })
  195. })
  196. })
  197. describe('setDocument', function () {
  198. beforeEach(function () {
  199. this.req.params = {
  200. Project_id: this.project_id,
  201. doc_id: this.doc_id,
  202. }
  203. })
  204. describe('when the document exists', function () {
  205. beforeEach(function () {
  206. this.ProjectEntityUpdateHandler.updateDocLines = sinon.stub().yields()
  207. this.req.body = {
  208. lines: this.doc_lines,
  209. version: this.version,
  210. ranges: this.ranges,
  211. lastUpdatedAt: this.lastUpdatedAt,
  212. lastUpdatedBy: this.lastUpdatedBy,
  213. }
  214. this.DocumentController.setDocument(this.req, this.res, this.next)
  215. })
  216. it('should update the document in Mongo', function () {
  217. sinon.assert.calledWith(
  218. this.ProjectEntityUpdateHandler.updateDocLines,
  219. this.project_id,
  220. this.doc_id,
  221. this.doc_lines,
  222. this.version,
  223. this.ranges,
  224. this.lastUpdatedAt,
  225. this.lastUpdatedBy
  226. )
  227. })
  228. it('should return a successful response', function () {
  229. this.res.success.should.equal(true)
  230. })
  231. })
  232. describe("when the document doesn't exist", function () {
  233. beforeEach(function () {
  234. this.ProjectEntityUpdateHandler.updateDocLines = sinon
  235. .stub()
  236. .yields(new Errors.NotFoundError('document does not exist'))
  237. this.req.body = { lines: this.doc_lines }
  238. this.DocumentController.setDocument(this.req, this.res, this.next)
  239. })
  240. it('should call next with the NotFoundError', function () {
  241. this.next
  242. .calledWith(sinon.match.instanceOf(Errors.NotFoundError))
  243. .should.equal(true)
  244. })
  245. })
  246. })
  247. })