CompileManagerTests.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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.ensureRootDocumentIsValid = 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.ensureRootDocumentIsValid
  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. })
  138. describe('when the project has been recently compiled', function() {
  139. it('should return', function(done) {
  140. this.CompileManager._checkIfAutoCompileLimitHasBeenHit = (
  141. isAutoCompile,
  142. compileGroup,
  143. cb
  144. ) => cb(null, true)
  145. this.CompileManager._checkIfRecentlyCompiled = sinon
  146. .stub()
  147. .callsArgWith(2, null, true)
  148. return this.CompileManager.compile(
  149. this.project_id,
  150. this.user_id,
  151. {},
  152. (err, status) => {
  153. status.should.equal('too-recently-compiled')
  154. return done()
  155. }
  156. )
  157. })
  158. })
  159. describe('should check the rate limit', function() {
  160. it('should return', function(done) {
  161. this.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
  162. .stub()
  163. .callsArgWith(2, null, false)
  164. return this.CompileManager.compile(
  165. this.project_id,
  166. this.user_id,
  167. {},
  168. (err, status) => {
  169. status.should.equal('autocompile-backoff')
  170. return done()
  171. }
  172. )
  173. })
  174. })
  175. })
  176. describe('getProjectCompileLimits', function() {
  177. beforeEach(function() {
  178. this.features = {
  179. compileTimeout: (this.timeout = 42),
  180. compileGroup: (this.group = 'priority')
  181. }
  182. this.ProjectGetter.getProject = sinon
  183. .stub()
  184. .callsArgWith(
  185. 2,
  186. null,
  187. (this.project = { owner_ref: (this.owner_id = 'owner-id-123') })
  188. )
  189. this.UserGetter.getUser = sinon
  190. .stub()
  191. .callsArgWith(2, null, (this.user = { features: this.features }))
  192. return this.CompileManager.getProjectCompileLimits(
  193. this.project_id,
  194. this.callback
  195. )
  196. })
  197. it('should look up the owner of the project', function() {
  198. return this.ProjectGetter.getProject
  199. .calledWith(this.project_id, { owner_ref: 1 })
  200. .should.equal(true)
  201. })
  202. it("should look up the owner's features", function() {
  203. return this.UserGetter.getUser
  204. .calledWith(this.project.owner_ref, { features: 1 })
  205. .should.equal(true)
  206. })
  207. it('should return the limits', function() {
  208. return this.callback
  209. .calledWith(null, {
  210. timeout: this.timeout,
  211. compileGroup: this.group
  212. })
  213. .should.equal(true)
  214. })
  215. })
  216. describe('deleteAuxFiles', function() {
  217. beforeEach(function() {
  218. this.CompileManager.getProjectCompileLimits = sinon
  219. .stub()
  220. .callsArgWith(
  221. 1,
  222. null,
  223. (this.limits = { compileGroup: 'mock-compile-group' })
  224. )
  225. this.ClsiManager.deleteAuxFiles = sinon.stub().callsArg(3)
  226. return this.CompileManager.deleteAuxFiles(
  227. this.project_id,
  228. this.user_id,
  229. this.callback
  230. )
  231. })
  232. it('should look up the compile group to use', function() {
  233. return this.CompileManager.getProjectCompileLimits
  234. .calledWith(this.project_id)
  235. .should.equal(true)
  236. })
  237. it('should delete the aux files', function() {
  238. return this.ClsiManager.deleteAuxFiles
  239. .calledWith(this.project_id, this.user_id, this.limits)
  240. .should.equal(true)
  241. })
  242. it('should call the callback', function() {
  243. return this.callback.called.should.equal(true)
  244. })
  245. })
  246. describe('_checkIfRecentlyCompiled', function() {
  247. describe('when the key exists in redis', function() {
  248. beforeEach(function() {
  249. this.rclient.set = sinon.stub().callsArgWith(5, null, null)
  250. return this.CompileManager._checkIfRecentlyCompiled(
  251. this.project_id,
  252. this.user_id,
  253. this.callback
  254. )
  255. })
  256. it('should try to set the key', function() {
  257. return this.rclient.set
  258. .calledWith(
  259. `compile:${this.project_id}:${this.user_id}`,
  260. true,
  261. 'EX',
  262. this.CompileManager.COMPILE_DELAY,
  263. 'NX'
  264. )
  265. .should.equal(true)
  266. })
  267. it('should call the callback with true', function() {
  268. return this.callback.calledWith(null, true).should.equal(true)
  269. })
  270. })
  271. describe('when the key does not exist in redis', function() {
  272. beforeEach(function() {
  273. this.rclient.set = sinon.stub().callsArgWith(5, null, 'OK')
  274. return this.CompileManager._checkIfRecentlyCompiled(
  275. this.project_id,
  276. this.user_id,
  277. this.callback
  278. )
  279. })
  280. it('should try to set the key', function() {
  281. return this.rclient.set
  282. .calledWith(
  283. `compile:${this.project_id}:${this.user_id}`,
  284. true,
  285. 'EX',
  286. this.CompileManager.COMPILE_DELAY,
  287. 'NX'
  288. )
  289. .should.equal(true)
  290. })
  291. it('should call the callback with false', function() {
  292. return this.callback.calledWith(null, false).should.equal(true)
  293. })
  294. })
  295. })
  296. describe('_checkIfAutoCompileLimitHasBeenHit', function() {
  297. it('should be able to compile if it is not an autocompile', function(done) {
  298. this.ratelimiter.addCount.callsArgWith(2, null, true)
  299. return this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
  300. false,
  301. 'everyone',
  302. (err, canCompile) => {
  303. canCompile.should.equal(true)
  304. return done()
  305. }
  306. )
  307. })
  308. it('should be able to compile if rate limit has remianing', function(done) {
  309. this.ratelimiter.addCount.callsArgWith(1, null, true)
  310. return this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
  311. true,
  312. 'everyone',
  313. (err, canCompile) => {
  314. const args = this.ratelimiter.addCount.args[0][0]
  315. args.throttle.should.equal(25)
  316. args.subjectName.should.equal('everyone')
  317. args.timeInterval.should.equal(20)
  318. args.endpointName.should.equal('auto_compile')
  319. canCompile.should.equal(true)
  320. return done()
  321. }
  322. )
  323. })
  324. it('should be not able to compile if rate limit has no remianing', function(done) {
  325. this.ratelimiter.addCount.callsArgWith(1, null, false)
  326. return this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
  327. true,
  328. 'everyone',
  329. (err, canCompile) => {
  330. canCompile.should.equal(false)
  331. return done()
  332. }
  333. )
  334. })
  335. it('should return false if there is an error in the rate limit', function(done) {
  336. this.ratelimiter.addCount.callsArgWith(1, 'error')
  337. return this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
  338. true,
  339. 'everyone',
  340. (err, canCompile) => {
  341. canCompile.should.equal(false)
  342. return done()
  343. }
  344. )
  345. })
  346. })
  347. describe('wordCount', function() {
  348. beforeEach(function() {
  349. this.CompileManager.getProjectCompileLimits = sinon
  350. .stub()
  351. .callsArgWith(
  352. 1,
  353. null,
  354. (this.limits = { compileGroup: 'mock-compile-group' })
  355. )
  356. this.ClsiManager.wordCount = sinon.stub().callsArg(4)
  357. return this.CompileManager.wordCount(
  358. this.project_id,
  359. this.user_id,
  360. false,
  361. this.callback
  362. )
  363. })
  364. it('should look up the compile group to use', function() {
  365. return this.CompileManager.getProjectCompileLimits
  366. .calledWith(this.project_id)
  367. .should.equal(true)
  368. })
  369. it('should call wordCount for project', function() {
  370. return this.ClsiManager.wordCount
  371. .calledWith(this.project_id, this.user_id, false, this.limits)
  372. .should.equal(true)
  373. })
  374. it('should call the callback', function() {
  375. return this.callback.called.should.equal(true)
  376. })
  377. })
  378. })