CompileManagerTests.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /* eslint-disable
  2. node/handle-callback-err,
  3. max-len,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS206: Consider reworking classes to avoid initClass
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const sinon = require('sinon')
  16. const { assert, expect } = require('chai')
  17. const modulePath = '../../../../app/src/Features/Compile/CompileManager.js'
  18. const SandboxedModule = require('sandboxed-module')
  19. describe('CompileManager', function () {
  20. beforeEach(function () {
  21. let Timer
  22. this.rateLimitGetStub = sinon.stub()
  23. const { rateLimitGetStub } = this
  24. this.ratelimiter = { addCount: sinon.stub() }
  25. this.CompileManager = SandboxedModule.require(modulePath, {
  26. requires: {
  27. '@overleaf/settings': (this.settings = {
  28. redis: { web: { host: 'localhost', port: 42 } },
  29. rateLimit: { autoCompile: {} },
  30. }),
  31. '../../infrastructure/RedisWrapper': {
  32. client: () => {
  33. return (this.rclient = { auth() {} })
  34. },
  35. },
  36. '../Project/ProjectRootDocManager': (this.ProjectRootDocManager = {}),
  37. '../Project/ProjectGetter': (this.ProjectGetter = {}),
  38. '../User/UserGetter': (this.UserGetter = {}),
  39. './ClsiManager': (this.ClsiManager = {}),
  40. '../../infrastructure/RateLimiter': this.ratelimiter,
  41. '@overleaf/metrics': (this.Metrics = {
  42. Timer: (Timer = (function () {
  43. Timer = class Timer {
  44. static initClass() {
  45. this.prototype.done = sinon.stub()
  46. }
  47. }
  48. Timer.initClass()
  49. return Timer
  50. })()),
  51. inc: sinon.stub(),
  52. }),
  53. },
  54. })
  55. this.project_id = 'mock-project-id-123'
  56. this.user_id = 'mock-user-id-123'
  57. this.callback = sinon.stub()
  58. return (this.limits = {
  59. timeout: 42,
  60. })
  61. })
  62. describe('compile', function () {
  63. beforeEach(function () {
  64. this.CompileManager._checkIfRecentlyCompiled = sinon
  65. .stub()
  66. .callsArgWith(2, null, false)
  67. this.ProjectRootDocManager.ensureRootDocumentIsSet = sinon
  68. .stub()
  69. .callsArgWith(1, null)
  70. this.CompileManager.getProjectCompileLimits = sinon
  71. .stub()
  72. .callsArgWith(1, null, this.limits)
  73. return (this.ClsiManager.sendRequest = sinon
  74. .stub()
  75. .callsArgWith(
  76. 3,
  77. null,
  78. (this.status = 'mock-status'),
  79. (this.outputFiles = 'mock output files'),
  80. (this.output = 'mock output')
  81. ))
  82. })
  83. describe('succesfully', function () {
  84. beforeEach(function () {
  85. this.CompileManager._checkIfAutoCompileLimitHasBeenHit = (
  86. isAutoCompile,
  87. compileGroup,
  88. cb
  89. ) => cb(null, true)
  90. return this.CompileManager.compile(
  91. this.project_id,
  92. this.user_id,
  93. {},
  94. this.callback
  95. )
  96. })
  97. it('should check the project has not been recently compiled', function () {
  98. return this.CompileManager._checkIfRecentlyCompiled
  99. .calledWith(this.project_id, this.user_id)
  100. .should.equal(true)
  101. })
  102. it('should ensure that the root document is set', function () {
  103. return this.ProjectRootDocManager.ensureRootDocumentIsSet
  104. .calledWith(this.project_id)
  105. .should.equal(true)
  106. })
  107. it('should get the project compile limits', function () {
  108. return this.CompileManager.getProjectCompileLimits
  109. .calledWith(this.project_id)
  110. .should.equal(true)
  111. })
  112. it('should run the compile with the compile limits', function () {
  113. return this.ClsiManager.sendRequest
  114. .calledWith(this.project_id, this.user_id, {
  115. timeout: this.limits.timeout,
  116. })
  117. .should.equal(true)
  118. })
  119. it('should call the callback with the output', function () {
  120. return this.callback
  121. .calledWith(null, this.status, this.outputFiles, this.output)
  122. .should.equal(true)
  123. })
  124. it('should time the compile', function () {
  125. return this.Metrics.Timer.prototype.done.called.should.equal(true)
  126. })
  127. })
  128. describe('when the project has been recently compiled', function () {
  129. it('should return', function (done) {
  130. this.CompileManager._checkIfAutoCompileLimitHasBeenHit = (
  131. isAutoCompile,
  132. compileGroup,
  133. cb
  134. ) => cb(null, true)
  135. this.CompileManager._checkIfRecentlyCompiled = sinon
  136. .stub()
  137. .callsArgWith(2, null, true)
  138. return this.CompileManager.compile(
  139. this.project_id,
  140. this.user_id,
  141. {},
  142. (err, status) => {
  143. status.should.equal('too-recently-compiled')
  144. return done()
  145. }
  146. )
  147. })
  148. })
  149. describe('should check the rate limit', function () {
  150. it('should return', function (done) {
  151. this.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
  152. .stub()
  153. .callsArgWith(2, null, false)
  154. return this.CompileManager.compile(
  155. this.project_id,
  156. this.user_id,
  157. {},
  158. (err, status) => {
  159. status.should.equal('autocompile-backoff')
  160. return done()
  161. }
  162. )
  163. })
  164. })
  165. })
  166. describe('getProjectCompileLimits', function () {
  167. beforeEach(function () {
  168. this.features = {
  169. compileTimeout: (this.timeout = 42),
  170. compileGroup: (this.group = 'priority'),
  171. }
  172. this.ProjectGetter.getProject = sinon
  173. .stub()
  174. .callsArgWith(
  175. 2,
  176. null,
  177. (this.project = { owner_ref: (this.owner_id = 'owner-id-123') })
  178. )
  179. this.UserGetter.getUser = sinon
  180. .stub()
  181. .callsArgWith(2, null, (this.user = { features: this.features }))
  182. return this.CompileManager.getProjectCompileLimits(
  183. this.project_id,
  184. this.callback
  185. )
  186. })
  187. it('should look up the owner of the project', function () {
  188. return this.ProjectGetter.getProject
  189. .calledWith(this.project_id, { owner_ref: 1 })
  190. .should.equal(true)
  191. })
  192. it("should look up the owner's features", function () {
  193. return this.UserGetter.getUser
  194. .calledWith(this.project.owner_ref, {
  195. alphaProgram: 1,
  196. betaProgram: 1,
  197. features: 1,
  198. })
  199. .should.equal(true)
  200. })
  201. it('should return the limits', function () {
  202. return this.callback
  203. .calledWith(null, {
  204. timeout: this.timeout,
  205. compileGroup: this.group,
  206. })
  207. .should.equal(true)
  208. })
  209. })
  210. describe('deleteAuxFiles', function () {
  211. beforeEach(function () {
  212. this.CompileManager.getProjectCompileLimits = sinon
  213. .stub()
  214. .callsArgWith(
  215. 1,
  216. null,
  217. (this.limits = { compileGroup: 'mock-compile-group' })
  218. )
  219. this.ClsiManager.deleteAuxFiles = sinon.stub().callsArg(3)
  220. return this.CompileManager.deleteAuxFiles(
  221. this.project_id,
  222. this.user_id,
  223. this.callback
  224. )
  225. })
  226. it('should look up the compile group to use', function () {
  227. return this.CompileManager.getProjectCompileLimits
  228. .calledWith(this.project_id)
  229. .should.equal(true)
  230. })
  231. it('should delete the aux files', function () {
  232. return this.ClsiManager.deleteAuxFiles
  233. .calledWith(this.project_id, this.user_id, this.limits)
  234. .should.equal(true)
  235. })
  236. it('should call the callback', function () {
  237. return this.callback.called.should.equal(true)
  238. })
  239. })
  240. describe('_checkIfRecentlyCompiled', function () {
  241. describe('when the key exists in redis', function () {
  242. beforeEach(function () {
  243. this.rclient.set = sinon.stub().callsArgWith(5, null, null)
  244. return this.CompileManager._checkIfRecentlyCompiled(
  245. this.project_id,
  246. this.user_id,
  247. this.callback
  248. )
  249. })
  250. it('should try to set the key', function () {
  251. return this.rclient.set
  252. .calledWith(
  253. `compile:${this.project_id}:${this.user_id}`,
  254. true,
  255. 'EX',
  256. this.CompileManager.COMPILE_DELAY,
  257. 'NX'
  258. )
  259. .should.equal(true)
  260. })
  261. it('should call the callback with true', function () {
  262. return this.callback.calledWith(null, true).should.equal(true)
  263. })
  264. })
  265. describe('when the key does not exist in redis', function () {
  266. beforeEach(function () {
  267. this.rclient.set = sinon.stub().callsArgWith(5, null, 'OK')
  268. return this.CompileManager._checkIfRecentlyCompiled(
  269. this.project_id,
  270. this.user_id,
  271. this.callback
  272. )
  273. })
  274. it('should try to set the key', function () {
  275. return this.rclient.set
  276. .calledWith(
  277. `compile:${this.project_id}:${this.user_id}`,
  278. true,
  279. 'EX',
  280. this.CompileManager.COMPILE_DELAY,
  281. 'NX'
  282. )
  283. .should.equal(true)
  284. })
  285. it('should call the callback with false', function () {
  286. return this.callback.calledWith(null, false).should.equal(true)
  287. })
  288. })
  289. })
  290. describe('_checkIfAutoCompileLimitHasBeenHit', function () {
  291. it('should be able to compile if it is not an autocompile', function (done) {
  292. this.ratelimiter.addCount.callsArgWith(2, null, true)
  293. return this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
  294. false,
  295. 'everyone',
  296. (err, canCompile) => {
  297. canCompile.should.equal(true)
  298. return done()
  299. }
  300. )
  301. })
  302. it('should be able to compile if rate limit has remianing', function (done) {
  303. this.ratelimiter.addCount.callsArgWith(1, null, true)
  304. return this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
  305. true,
  306. 'everyone',
  307. (err, canCompile) => {
  308. const args = this.ratelimiter.addCount.args[0][0]
  309. args.throttle.should.equal(25)
  310. args.subjectName.should.equal('everyone')
  311. args.timeInterval.should.equal(20)
  312. args.endpointName.should.equal('auto_compile')
  313. canCompile.should.equal(true)
  314. return done()
  315. }
  316. )
  317. })
  318. it('should be not able to compile if rate limit has no remianing', function (done) {
  319. this.ratelimiter.addCount.callsArgWith(1, null, false)
  320. return this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
  321. true,
  322. 'everyone',
  323. (err, canCompile) => {
  324. canCompile.should.equal(false)
  325. return done()
  326. }
  327. )
  328. })
  329. it('should return false if there is an error in the rate limit', function (done) {
  330. this.ratelimiter.addCount.callsArgWith(1, 'error')
  331. return this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
  332. true,
  333. 'everyone',
  334. (err, canCompile) => {
  335. canCompile.should.equal(false)
  336. return done()
  337. }
  338. )
  339. })
  340. })
  341. describe('wordCount', function () {
  342. beforeEach(function () {
  343. this.CompileManager.getProjectCompileLimits = sinon
  344. .stub()
  345. .callsArgWith(
  346. 1,
  347. null,
  348. (this.limits = { compileGroup: 'mock-compile-group' })
  349. )
  350. this.ClsiManager.wordCount = sinon.stub().callsArg(4)
  351. return this.CompileManager.wordCount(
  352. this.project_id,
  353. this.user_id,
  354. false,
  355. this.callback
  356. )
  357. })
  358. it('should look up the compile group to use', function () {
  359. return this.CompileManager.getProjectCompileLimits
  360. .calledWith(this.project_id)
  361. .should.equal(true)
  362. })
  363. it('should call wordCount for project', function () {
  364. return this.ClsiManager.wordCount
  365. .calledWith(this.project_id, this.user_id, false, this.limits)
  366. .should.equal(true)
  367. })
  368. it('should call the callback', function () {
  369. return this.callback.called.should.equal(true)
  370. })
  371. })
  372. })