TpdsControllerTests.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. const SandboxedModule = require('sandboxed-module')
  2. const sinon = require('sinon')
  3. const Errors = require('../../../../app/src/Features/Errors/Errors')
  4. const modulePath = require('path').join(
  5. __dirname,
  6. '../../../../app/src/Features/ThirdPartyDataStore/TpdsController.js'
  7. )
  8. describe('TpdsController', function () {
  9. beforeEach(function () {
  10. this.TpdsUpdateHandler = {}
  11. this.SessionManager = {
  12. getLoggedInUserId: sinon.stub().returns('user-id'),
  13. }
  14. this.TpdsQueueManager = {
  15. promises: {
  16. getQueues: sinon.stub().returns('queues'),
  17. },
  18. }
  19. this.TpdsController = SandboxedModule.require(modulePath, {
  20. requires: {
  21. './TpdsUpdateHandler': this.TpdsUpdateHandler,
  22. './UpdateMerger': (this.UpdateMerger = {}),
  23. '../Notifications/NotificationsBuilder': (this.NotificationsBuilder = {
  24. tpdsFileLimit: sinon.stub().returns({ create: sinon.stub() }),
  25. }),
  26. '../Authentication/SessionManager': this.SessionManager,
  27. './TpdsQueueManager': this.TpdsQueueManager,
  28. '@overleaf/metrics': {
  29. inc() {},
  30. },
  31. },
  32. })
  33. this.user_id = 'dsad29jlkjas'
  34. })
  35. describe('getting an update', function () {
  36. it('should process the update with the update receiver', function (done) {
  37. const path = '/projectName/here.txt'
  38. const req = {
  39. pause() {},
  40. params: { 0: path, user_id: this.user_id },
  41. session: {
  42. destroy() {},
  43. },
  44. headers: {
  45. 'x-sl-update-source': (this.source = 'dropbox'),
  46. },
  47. }
  48. this.TpdsUpdateHandler.newUpdate = sinon.stub().callsArg(5)
  49. const res = {
  50. sendStatus: () => {
  51. this.TpdsUpdateHandler.newUpdate
  52. .calledWith(
  53. this.user_id,
  54. 'projectName',
  55. '/here.txt',
  56. req,
  57. this.source
  58. )
  59. .should.equal(true)
  60. done()
  61. },
  62. }
  63. this.TpdsController.mergeUpdate(req, res)
  64. })
  65. it('should return a 500 error when the update receiver fails', function () {
  66. const path = '/projectName/here.txt'
  67. const req = {
  68. pause() {},
  69. params: { 0: path, user_id: this.user_id },
  70. session: {
  71. destroy() {},
  72. },
  73. headers: {
  74. 'x-sl-update-source': (this.source = 'dropbox'),
  75. },
  76. }
  77. this.TpdsUpdateHandler.newUpdate = sinon
  78. .stub()
  79. .callsArgWith(5, 'update-receiver-error')
  80. const res = {
  81. sendStatus: sinon.stub(),
  82. }
  83. this.TpdsController.mergeUpdate(req, res)
  84. res.sendStatus.calledWith(500).should.equal(true)
  85. })
  86. it('should return a 400 error when the project is too big', function () {
  87. const path = '/projectName/here.txt'
  88. const req = {
  89. pause() {},
  90. params: { 0: path, user_id: this.user_id, projectName: 'projectName' },
  91. session: {
  92. destroy() {},
  93. },
  94. headers: {
  95. 'x-sl-update-source': (this.source = 'dropbox'),
  96. },
  97. }
  98. this.TpdsUpdateHandler.newUpdate = sinon
  99. .stub()
  100. .callsArgWith(5, { message: 'project_has_too_many_files' })
  101. const res = {
  102. sendStatus: sinon.stub(),
  103. }
  104. this.TpdsController.mergeUpdate(req, res)
  105. res.sendStatus.calledWith(400).should.equal(true)
  106. this.NotificationsBuilder.tpdsFileLimit
  107. .calledWith(this.user_id)
  108. .should.equal(true)
  109. })
  110. it('should return a 429 error when the update receiver fails due to too many requests error', function () {
  111. const path = '/projectName/here.txt'
  112. const req = {
  113. pause() {},
  114. params: { 0: path, user_id: this.user_id },
  115. session: {
  116. destroy() {},
  117. },
  118. headers: {
  119. 'x-sl-update-source': (this.source = 'dropbox'),
  120. },
  121. }
  122. this.TpdsUpdateHandler.newUpdate = sinon
  123. .stub()
  124. .callsArgWith(5, new Errors.TooManyRequestsError('project on cooldown'))
  125. const res = {
  126. sendStatus: sinon.stub(),
  127. }
  128. this.TpdsController.mergeUpdate(req, res)
  129. res.sendStatus.calledWith(429).should.equal(true)
  130. })
  131. })
  132. describe('getting a delete update', function () {
  133. it('should process the delete with the update receiver', function (done) {
  134. const path = '/projectName/here.txt'
  135. const req = {
  136. params: { 0: path, user_id: this.user_id },
  137. session: {
  138. destroy() {},
  139. },
  140. headers: {
  141. 'x-sl-update-source': (this.source = 'dropbox'),
  142. },
  143. }
  144. this.TpdsUpdateHandler.deleteUpdate = sinon.stub().callsArg(4)
  145. const res = {
  146. sendStatus: () => {
  147. this.TpdsUpdateHandler.deleteUpdate
  148. .calledWith(this.user_id, 'projectName', '/here.txt', this.source)
  149. .should.equal(true)
  150. done()
  151. },
  152. }
  153. this.TpdsController.deleteUpdate(req, res)
  154. })
  155. })
  156. describe('parseParams', function () {
  157. it('should take the project name off the start and replace with slash', function () {
  158. const path = 'noSlashHere'
  159. const req = { params: { 0: path, user_id: this.user_id } }
  160. const result = this.TpdsController.parseParams(req)
  161. result.userId.should.equal(this.user_id)
  162. result.filePath.should.equal('/')
  163. result.projectName.should.equal(path)
  164. })
  165. it('should take the project name off the start and it with no slashes in', function () {
  166. const path = '/project/file.tex'
  167. const req = { params: { 0: path, user_id: this.user_id } }
  168. const result = this.TpdsController.parseParams(req)
  169. result.userId.should.equal(this.user_id)
  170. result.filePath.should.equal('/file.tex')
  171. result.projectName.should.equal('project')
  172. })
  173. it('should take the project name of and return a slash for the file path', function () {
  174. const path = '/project_name'
  175. const req = { params: { 0: path, user_id: this.user_id } }
  176. const result = this.TpdsController.parseParams(req)
  177. result.projectName.should.equal('project_name')
  178. result.filePath.should.equal('/')
  179. })
  180. })
  181. describe('updateProjectContents', function () {
  182. beforeEach(function () {
  183. this.UpdateMerger.mergeUpdate = sinon.stub().callsArg(5)
  184. this.req = {
  185. params: {
  186. 0: (this.path = 'chapters/main.tex'),
  187. project_id: (this.project_id = 'project-id-123'),
  188. },
  189. session: {
  190. destroy: sinon.stub(),
  191. },
  192. headers: {
  193. 'x-sl-update-source': (this.source = 'github'),
  194. },
  195. }
  196. this.res = { sendStatus: sinon.stub() }
  197. this.TpdsController.updateProjectContents(this.req, this.res, this.next)
  198. })
  199. it('should merge the update', function () {
  200. this.UpdateMerger.mergeUpdate
  201. .calledWith(
  202. null,
  203. this.project_id,
  204. `/${this.path}`,
  205. this.req,
  206. this.source
  207. )
  208. .should.equal(true)
  209. })
  210. it('should return a success', function () {
  211. this.res.sendStatus.calledWith(200).should.equal(true)
  212. })
  213. })
  214. describe('deleteProjectContents', function () {
  215. beforeEach(function () {
  216. this.UpdateMerger.deleteUpdate = sinon.stub().callsArg(4)
  217. this.req = {
  218. params: {
  219. 0: (this.path = 'chapters/main.tex'),
  220. project_id: (this.project_id = 'project-id-123'),
  221. },
  222. session: {
  223. destroy: sinon.stub(),
  224. },
  225. headers: {
  226. 'x-sl-update-source': (this.source = 'github'),
  227. },
  228. }
  229. this.res = { sendStatus: sinon.stub() }
  230. this.TpdsController.deleteProjectContents(this.req, this.res, this.next)
  231. })
  232. it('should delete the file', function () {
  233. this.UpdateMerger.deleteUpdate
  234. .calledWith(null, this.project_id, `/${this.path}`, this.source)
  235. .should.equal(true)
  236. })
  237. it('should return a success', function () {
  238. this.res.sendStatus.calledWith(200).should.equal(true)
  239. })
  240. })
  241. describe('getQueues', function () {
  242. beforeEach(function () {
  243. this.req = {}
  244. this.res = { json: sinon.stub() }
  245. this.next = sinon.stub()
  246. })
  247. describe('success', function () {
  248. beforeEach(async function () {
  249. await this.TpdsController.getQueues(this.req, this.res, this.next)
  250. })
  251. it('should use userId from session', function () {
  252. this.SessionManager.getLoggedInUserId.should.have.been.calledOnce
  253. this.TpdsQueueManager.promises.getQueues.should.have.been.calledWith(
  254. 'user-id'
  255. )
  256. })
  257. it('should call json with response', function () {
  258. this.res.json.should.have.been.calledWith('queues')
  259. this.next.should.not.have.been.called
  260. })
  261. })
  262. describe('error', function () {
  263. beforeEach(async function () {
  264. this.err = new Error()
  265. this.TpdsQueueManager.promises.getQueues = sinon
  266. .stub()
  267. .rejects(this.err)
  268. await this.TpdsController.getQueues(this.req, this.res, this.next)
  269. })
  270. it('should call next with error', function () {
  271. this.res.json.should.not.have.been.called
  272. this.next.should.have.been.calledWith(this.err)
  273. })
  274. })
  275. })
  276. })