CompileManagerTests.js 12 KB

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