DocumentControllerTests.mjs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import sinon from 'sinon'
  2. import esmock from 'esmock'
  3. import MockRequest from '../helpers/MockRequest.js'
  4. import MockResponse from '../helpers/MockResponse.js'
  5. import Errors from '../../../../app/src/Features/Errors/Errors.js'
  6. const MODULE_PATH =
  7. '../../../../app/src/Features/Documents/DocumentController.mjs'
  8. describe('DocumentController', function () {
  9. beforeEach(async function () {
  10. this.res = new MockResponse()
  11. this.req = new MockRequest()
  12. this.next = sinon.stub()
  13. this.doc = { _id: 'doc-id-123' }
  14. this.doc_lines = ['one', 'two', 'three']
  15. this.version = 42
  16. this.ranges = {
  17. comments: [
  18. {
  19. id: 'comment1',
  20. op: {
  21. c: 'foo',
  22. p: 123,
  23. t: 'comment1',
  24. },
  25. },
  26. {
  27. id: 'comment2',
  28. op: {
  29. c: 'bar',
  30. p: 456,
  31. t: 'comment2',
  32. },
  33. },
  34. ],
  35. }
  36. this.pathname = '/a/b/c/file.tex'
  37. this.lastUpdatedAt = new Date().getTime()
  38. this.lastUpdatedBy = 'fake-last-updater-id'
  39. this.rev = 5
  40. this.project = {
  41. _id: 'project-id-123',
  42. overleaf: {
  43. history: {
  44. id: 1234,
  45. display: true,
  46. },
  47. },
  48. }
  49. this.resolvedThreadIds = [
  50. 'comment2',
  51. 'comment4', // Comment in project but not in doc
  52. ]
  53. this.ProjectGetter = {
  54. promises: {
  55. getProject: sinon.stub().resolves(this.project),
  56. },
  57. }
  58. this.ProjectLocator = {
  59. promises: {
  60. findElement: sinon
  61. .stub()
  62. .resolves({ element: this.doc, path: { fileSystem: this.pathname } }),
  63. },
  64. }
  65. this.ProjectEntityHandler = {
  66. promises: {
  67. getDoc: sinon.stub().resolves({
  68. lines: this.doc_lines,
  69. rev: this.rev,
  70. version: this.version,
  71. ranges: this.ranges,
  72. }),
  73. },
  74. }
  75. this.ProjectEntityUpdateHandler = {
  76. promises: {
  77. updateDocLines: sinon.stub().resolves(),
  78. },
  79. }
  80. this.ChatApiHandler = {
  81. promises: {
  82. getResolvedThreadIds: sinon.stub().resolves(this.resolvedThreadIds),
  83. },
  84. }
  85. this.DocumentController = await esmock.strict(MODULE_PATH, {
  86. '../../../../app/src/Features/Project/ProjectGetter': this.ProjectGetter,
  87. '../../../../app/src/Features/Project/ProjectLocator':
  88. this.ProjectLocator,
  89. '../../../../app/src/Features/Project/ProjectEntityHandler':
  90. this.ProjectEntityHandler,
  91. '../../../../app/src/Features/Project/ProjectEntityUpdateHandler':
  92. this.ProjectEntityUpdateHandler,
  93. '../../../../app/src/Features/Chat/ChatApiHandler': this.ChatApiHandler,
  94. })
  95. })
  96. describe('getDocument', function () {
  97. beforeEach(function () {
  98. this.req.params = {
  99. Project_id: this.project._id,
  100. doc_id: this.doc._id,
  101. }
  102. })
  103. describe('when project exists with project history enabled', function () {
  104. beforeEach(function (done) {
  105. this.res.callback = err => {
  106. done(err)
  107. }
  108. this.DocumentController.getDocument(this.req, this.res, this.next)
  109. })
  110. it('should return the history id and display setting to the client as JSON', function () {
  111. this.res.type.should.equal('application/json')
  112. JSON.parse(this.res.body).should.deep.equal({
  113. lines: this.doc_lines,
  114. version: this.version,
  115. ranges: this.ranges,
  116. pathname: this.pathname,
  117. projectHistoryId: this.project.overleaf.history.id,
  118. projectHistoryType: 'project-history',
  119. resolvedCommentIds: ['comment2'],
  120. historyRangesSupport: false,
  121. otMigrationStage: 0,
  122. })
  123. })
  124. })
  125. describe('when the project does not exist', function () {
  126. beforeEach(function (done) {
  127. this.ProjectGetter.promises.getProject.resolves(null)
  128. this.res.callback = err => {
  129. done(err)
  130. }
  131. this.DocumentController.getDocument(this.req, this.res, this.next)
  132. })
  133. it('returns a 404', function () {
  134. this.res.statusCode.should.equal(404)
  135. })
  136. })
  137. })
  138. describe('setDocument', function () {
  139. beforeEach(function () {
  140. this.req.params = {
  141. Project_id: this.project._id,
  142. doc_id: this.doc._id,
  143. }
  144. })
  145. describe('when the document exists', function () {
  146. beforeEach(function (done) {
  147. this.req.body = {
  148. lines: this.doc_lines,
  149. version: this.version,
  150. ranges: this.ranges,
  151. lastUpdatedAt: this.lastUpdatedAt,
  152. lastUpdatedBy: this.lastUpdatedBy,
  153. }
  154. this.res.callback = err => {
  155. done(err)
  156. }
  157. this.DocumentController.setDocument(this.req, this.res, this.next)
  158. })
  159. it('should update the document in Mongo', function () {
  160. sinon.assert.calledWith(
  161. this.ProjectEntityUpdateHandler.promises.updateDocLines,
  162. this.project._id,
  163. this.doc._id,
  164. this.doc_lines,
  165. this.version,
  166. this.ranges,
  167. this.lastUpdatedAt,
  168. this.lastUpdatedBy
  169. )
  170. })
  171. it('should return a successful response', function () {
  172. this.res.success.should.equal(true)
  173. })
  174. })
  175. describe("when the document doesn't exist", function () {
  176. beforeEach(function (done) {
  177. this.ProjectEntityUpdateHandler.promises.updateDocLines.rejects(
  178. new Errors.NotFoundError('document does not exist')
  179. )
  180. this.req.body = { lines: this.doc_lines }
  181. this.next.callsFake(() => {
  182. done()
  183. })
  184. this.DocumentController.setDocument(this.req, this.res, this.next)
  185. })
  186. it('should call next with the NotFoundError', function () {
  187. this.next
  188. .calledWith(sinon.match.instanceOf(Errors.NotFoundError))
  189. .should.equal(true)
  190. })
  191. })
  192. })
  193. })