CompileControllerTests.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. /* eslint-disable
  2. camelcase,
  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. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const sinon = require('sinon')
  15. const chai = require('chai')
  16. const should = chai.should()
  17. const { assert } = require('chai')
  18. const { expect } = chai
  19. const modulePath = '../../../../app/src/Features/Compile/CompileController.js'
  20. const SandboxedModule = require('sandboxed-module')
  21. const MockRequest = require('../helpers/MockRequest')
  22. const MockResponse = require('../helpers/MockResponse')
  23. describe('CompileController', function() {
  24. beforeEach(function() {
  25. this.user_id = 'wat'
  26. this.user = {
  27. _id: this.user_id,
  28. email: 'user@example.com',
  29. features: {
  30. compileGroup: 'premium',
  31. compileTimeout: 100
  32. }
  33. }
  34. this.CompileManager = { compile: sinon.stub() }
  35. this.ClsiManager = {}
  36. this.UserGetter = { getUser: sinon.stub() }
  37. this.RateLimiter = { addCount: sinon.stub() }
  38. this.settings = {
  39. apis: {
  40. clsi: {
  41. url: 'clsi.example.com'
  42. },
  43. clsi_priority: {
  44. url: 'clsi-priority.example.com'
  45. }
  46. },
  47. defaultFeatures: {
  48. compileGroup: 'standard',
  49. compileTimeout: 60
  50. }
  51. }
  52. this.jar = { cookie: 'stuff' }
  53. this.ClsiCookieManager = {
  54. getCookieJar: sinon.stub().callsArgWith(1, null, this.jar)
  55. }
  56. this.AuthenticationController = {
  57. getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user),
  58. getLoggedInUserId: sinon.stub().returns(this.user_id),
  59. getSessionUser: sinon.stub().returns(this.user),
  60. isUserLoggedIn: sinon.stub().returns(true)
  61. }
  62. this.CompileController = SandboxedModule.require(modulePath, {
  63. globals: {
  64. console: console
  65. },
  66. requires: {
  67. 'settings-sharelatex': this.settings,
  68. request: (this.request = sinon.stub()),
  69. '../Project/ProjectGetter': (this.ProjectGetter = {}),
  70. 'logger-sharelatex': (this.logger = {
  71. log: sinon.stub(),
  72. error: sinon.stub()
  73. }),
  74. 'metrics-sharelatex': (this.Metrics = { inc: sinon.stub() }),
  75. './CompileManager': this.CompileManager,
  76. '../User/UserGetter': this.UserGetter,
  77. './ClsiManager': this.ClsiManager,
  78. '../Authentication/AuthenticationController': this
  79. .AuthenticationController,
  80. '../../infrastructure/RateLimiter': this.RateLimiter,
  81. './ClsiCookieManager': () => this.ClsiCookieManager
  82. }
  83. })
  84. this.project_id = 'project-id'
  85. this.next = sinon.stub()
  86. this.req = new MockRequest()
  87. return (this.res = new MockResponse())
  88. })
  89. describe('compile', function() {
  90. beforeEach(function() {
  91. this.req.params = { Project_id: this.project_id }
  92. this.req.session = {}
  93. return (this.CompileManager.compile = sinon
  94. .stub()
  95. .callsArgWith(
  96. 3,
  97. null,
  98. (this.status = 'success'),
  99. (this.outputFiles = ['mock-output-files'])
  100. ))
  101. })
  102. describe('when not an auto compile', function() {
  103. beforeEach(function() {
  104. return this.CompileController.compile(this.req, this.res, this.next)
  105. })
  106. it('should look up the user id', function() {
  107. return this.AuthenticationController.getLoggedInUserId
  108. .calledWith(this.req)
  109. .should.equal(true)
  110. })
  111. it('should do the compile without the auto compile flag', function() {
  112. return this.CompileManager.compile
  113. .calledWith(this.project_id, this.user_id, { isAutoCompile: false })
  114. .should.equal(true)
  115. })
  116. it('should set the content-type of the response to application/json', function() {
  117. return this.res.contentType
  118. .calledWith('application/json')
  119. .should.equal(true)
  120. })
  121. it('should send a successful response reporting the status and files', function() {
  122. this.res.statusCode.should.equal(200)
  123. return this.res.body.should.equal(
  124. JSON.stringify({
  125. status: this.status,
  126. outputFiles: this.outputFiles
  127. })
  128. )
  129. })
  130. })
  131. describe('when an auto compile', function() {
  132. beforeEach(function() {
  133. this.req.query = { auto_compile: 'true' }
  134. return this.CompileController.compile(this.req, this.res, this.next)
  135. })
  136. it('should do the compile with the auto compile flag', function() {
  137. return this.CompileManager.compile
  138. .calledWith(this.project_id, this.user_id, { isAutoCompile: true })
  139. .should.equal(true)
  140. })
  141. })
  142. describe('with the draft attribute', function() {
  143. beforeEach(function() {
  144. this.req.body = { draft: true }
  145. return this.CompileController.compile(this.req, this.res, this.next)
  146. })
  147. it('should do the compile without the draft compile flag', function() {
  148. return this.CompileManager.compile
  149. .calledWith(this.project_id, this.user_id, {
  150. isAutoCompile: false,
  151. draft: true
  152. })
  153. .should.equal(true)
  154. })
  155. })
  156. })
  157. describe('compileSubmission', function() {
  158. beforeEach(function() {
  159. this.submission_id = 'sub-1234'
  160. this.req.params = { submission_id: this.submission_id }
  161. this.req.body = {}
  162. return (this.ClsiManager.sendExternalRequest = sinon
  163. .stub()
  164. .callsArgWith(
  165. 3,
  166. null,
  167. (this.status = 'success'),
  168. (this.outputFiles = ['mock-output-files']),
  169. (this.clsiServerId = 'mock-server-id'),
  170. (this.validationProblems = null)
  171. ))
  172. })
  173. it('should set the content-type of the response to application/json', function() {
  174. this.CompileController.compileSubmission(this.req, this.res, this.next)
  175. return this.res.contentType
  176. .calledWith('application/json')
  177. .should.equal(true)
  178. })
  179. it('should send a successful response reporting the status and files', function() {
  180. this.CompileController.compileSubmission(this.req, this.res, this.next)
  181. this.res.statusCode.should.equal(200)
  182. return this.res.body.should.equal(
  183. JSON.stringify({
  184. status: this.status,
  185. outputFiles: this.outputFiles,
  186. clsiServerId: 'mock-server-id',
  187. validationProblems: null
  188. })
  189. )
  190. })
  191. describe('with compileGroup and timeout', function() {
  192. beforeEach(function() {
  193. this.req.body = {
  194. compileGroup: 'special',
  195. timeout: 600
  196. }
  197. return this.CompileController.compileSubmission(
  198. this.req,
  199. this.res,
  200. this.next
  201. )
  202. })
  203. it('should use the supplied values', function() {
  204. return this.ClsiManager.sendExternalRequest
  205. .calledWith(
  206. this.submission_id,
  207. { compileGroup: 'special', timeout: 600 },
  208. { compileGroup: 'special', timeout: 600 }
  209. )
  210. .should.equal(true)
  211. })
  212. })
  213. describe('with other supported options but not compileGroup and timeout', function() {
  214. beforeEach(function() {
  215. this.req.body = {
  216. rootResourcePath: 'main.tex',
  217. compiler: 'lualatex',
  218. draft: true,
  219. check: 'validate'
  220. }
  221. return this.CompileController.compileSubmission(
  222. this.req,
  223. this.res,
  224. this.next
  225. )
  226. })
  227. it('should use the other options but default values for compileGroup and timeout', function() {
  228. return this.ClsiManager.sendExternalRequest
  229. .calledWith(
  230. this.submission_id,
  231. {
  232. rootResourcePath: 'main.tex',
  233. compiler: 'lualatex',
  234. draft: true,
  235. check: 'validate'
  236. },
  237. {
  238. rootResourcePath: 'main.tex',
  239. compiler: 'lualatex',
  240. draft: true,
  241. check: 'validate',
  242. compileGroup: 'standard',
  243. timeout: 60
  244. }
  245. )
  246. .should.equal(true)
  247. })
  248. })
  249. })
  250. describe('downloadPdf', function() {
  251. beforeEach(function() {
  252. this.req.params = { Project_id: this.project_id }
  253. this.req.query = { pdfng: true }
  254. this.project = { name: 'test namè' }
  255. return (this.ProjectGetter.getProject = sinon
  256. .stub()
  257. .callsArgWith(2, null, this.project))
  258. })
  259. describe('when downloading for embedding', function() {
  260. beforeEach(function() {
  261. this.CompileController.proxyToClsi = sinon.stub()
  262. this.RateLimiter.addCount.callsArgWith(1, null, true)
  263. return this.CompileController.downloadPdf(this.req, this.res, this.next)
  264. })
  265. it('should look up the project', function() {
  266. return this.ProjectGetter.getProject
  267. .calledWith(this.project_id, { name: 1 })
  268. .should.equal(true)
  269. })
  270. it('should set the content-type of the response to application/pdf', function() {
  271. return this.res.contentType
  272. .calledWith('application/pdf')
  273. .should.equal(true)
  274. })
  275. it('should set the content-disposition header with a safe version of the project name', function() {
  276. return this.res.setContentDisposition
  277. .calledWith('', { filename: 'test_nam_.pdf' })
  278. .should.equal(true)
  279. })
  280. it('should increment the pdf-downloads metric', function() {
  281. return this.Metrics.inc.calledWith('pdf-downloads').should.equal(true)
  282. })
  283. it('should proxy the PDF from the CLSI', function() {
  284. return this.CompileController.proxyToClsi
  285. .calledWith(
  286. this.project_id,
  287. `/project/${this.project_id}/user/${
  288. this.user_id
  289. }/output/output.pdf`,
  290. this.req,
  291. this.res,
  292. this.next
  293. )
  294. .should.equal(true)
  295. })
  296. })
  297. describe('when the a build-id is provided', function() {
  298. beforeEach(function() {
  299. this.req.params.build_id = this.buildId = '1234-5678'
  300. this.CompileController.proxyToClsi = sinon.stub()
  301. this.RateLimiter.addCount.callsArgWith(1, null, true)
  302. return this.CompileController.downloadPdf(this.req, this.res, this.next)
  303. })
  304. it('should proxy the PDF from the CLSI, with a build-id', function() {
  305. return this.CompileController.proxyToClsi
  306. .calledWith(
  307. this.project_id,
  308. `/project/${this.project_id}/user/${this.user_id}/build/${
  309. this.buildId
  310. }/output/output.pdf`,
  311. this.req,
  312. this.res,
  313. this.next
  314. )
  315. .should.equal(true)
  316. })
  317. })
  318. describe('when the pdf is not going to be used in pdfjs viewer', function() {
  319. it('should check the rate limiter when pdfng is not set', function(done) {
  320. this.req.query = {}
  321. this.RateLimiter.addCount.callsArgWith(1, null, true)
  322. this.CompileController.proxyToClsi = (project_id, url) => {
  323. this.RateLimiter.addCount.args[0][0].throttle.should.equal(1000)
  324. return done()
  325. }
  326. return this.CompileController.downloadPdf(this.req, this.res)
  327. })
  328. it('should check the rate limiter when pdfng is false', function(done) {
  329. this.req.query = { pdfng: false }
  330. this.RateLimiter.addCount.callsArgWith(1, null, true)
  331. this.CompileController.proxyToClsi = (project_id, url) => {
  332. this.RateLimiter.addCount.args[0][0].throttle.should.equal(1000)
  333. return done()
  334. }
  335. return this.CompileController.downloadPdf(this.req, this.res)
  336. })
  337. })
  338. })
  339. describe('getFileFromClsiWithoutUser', function() {
  340. beforeEach(function() {
  341. this.submission_id = 'sub-1234'
  342. this.build_id = 123456
  343. this.file = 'project.pdf'
  344. this.req.params = {
  345. submission_id: this.submission_id,
  346. build_id: this.build_id,
  347. file: this.file
  348. }
  349. this.req.body = {}
  350. this.expected_url = `/project/${this.submission_id}/build/${
  351. this.build_id
  352. }/output/${this.file}`
  353. return (this.CompileController.proxyToClsiWithLimits = sinon.stub())
  354. })
  355. describe('without limits specified', function() {
  356. beforeEach(function() {
  357. return this.CompileController.getFileFromClsiWithoutUser(
  358. this.req,
  359. this.res,
  360. this.next
  361. )
  362. })
  363. it('should proxy to CLSI with correct URL and default limits', function() {
  364. return this.CompileController.proxyToClsiWithLimits
  365. .calledWith(this.submission_id, this.expected_url, {
  366. compileGroup: 'standard'
  367. })
  368. .should.equal(true)
  369. })
  370. })
  371. describe('with limits specified', function() {
  372. beforeEach(function() {
  373. this.req.body = { compileTimeout: 600, compileGroup: 'special' }
  374. return this.CompileController.getFileFromClsiWithoutUser(
  375. this.req,
  376. this.res,
  377. this.next
  378. )
  379. })
  380. it('should proxy to CLSI with correct URL and specified limits', function() {
  381. return this.CompileController.proxyToClsiWithLimits
  382. .calledWith(this.submission_id, this.expected_url, {
  383. compileGroup: 'special'
  384. })
  385. .should.equal(true)
  386. })
  387. })
  388. })
  389. describe('proxyToClsi', function() {
  390. beforeEach(function() {
  391. this.request.returns(
  392. (this.proxy = {
  393. pipe: sinon.stub(),
  394. on: sinon.stub()
  395. })
  396. )
  397. this.upstream = {
  398. statusCode: 204,
  399. headers: { mock: 'header' }
  400. }
  401. this.req.method = 'mock-method'
  402. return (this.req.headers = {
  403. Mock: 'Headers',
  404. Range: '123-456',
  405. 'If-Range': 'abcdef',
  406. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
  407. })
  408. })
  409. describe('old pdf viewer', function() {
  410. describe('user with standard priority', function() {
  411. beforeEach(function() {
  412. this.CompileManager.getProjectCompileLimits = sinon
  413. .stub()
  414. .callsArgWith(1, null, { compileGroup: 'standard' })
  415. return this.CompileController.proxyToClsi(
  416. this.project_id,
  417. (this.url = '/test'),
  418. this.req,
  419. this.res,
  420. this.next
  421. )
  422. })
  423. it('should open a request to the CLSI', function() {
  424. return this.request
  425. .calledWith({
  426. jar: this.jar,
  427. method: this.req.method,
  428. url: `${this.settings.apis.clsi.url}${this.url}`,
  429. timeout: 60 * 1000
  430. })
  431. .should.equal(true)
  432. })
  433. it('should pass the request on to the client', function() {
  434. return this.proxy.pipe.calledWith(this.res).should.equal(true)
  435. })
  436. it('should bind an error handle to the request proxy', function() {
  437. return this.proxy.on.calledWith('error').should.equal(true)
  438. })
  439. })
  440. describe('user with priority compile', () =>
  441. beforeEach(function() {
  442. this.CompileManager.getProjectCompileLimits = sinon
  443. .stub()
  444. .callsArgWith(1, null, { compileGroup: 'priority' })
  445. return this.CompileController.proxyToClsi(
  446. this.project_id,
  447. (this.url = '/test'),
  448. this.req,
  449. this.res,
  450. this.next
  451. )
  452. }))
  453. describe('user with standard priority via query string', function() {
  454. beforeEach(function() {
  455. this.req.query = { compileGroup: 'standard' }
  456. return this.CompileController.proxyToClsi(
  457. this.project_id,
  458. (this.url = '/test'),
  459. this.req,
  460. this.res,
  461. this.next
  462. )
  463. })
  464. it('should open a request to the CLSI', function() {
  465. return this.request
  466. .calledWith({
  467. jar: this.jar,
  468. method: this.req.method,
  469. url: `${this.settings.apis.clsi.url}${this.url}`,
  470. timeout: 60 * 1000
  471. })
  472. .should.equal(true)
  473. })
  474. it('should pass the request on to the client', function() {
  475. return this.proxy.pipe.calledWith(this.res).should.equal(true)
  476. })
  477. it('should bind an error handle to the request proxy', function() {
  478. return this.proxy.on.calledWith('error').should.equal(true)
  479. })
  480. })
  481. describe('user with non-existent priority via query string', function() {
  482. beforeEach(function() {
  483. this.req.query = { compileGroup: 'foobar' }
  484. return this.CompileController.proxyToClsi(
  485. this.project_id,
  486. (this.url = '/test'),
  487. this.req,
  488. this.res,
  489. this.next
  490. )
  491. })
  492. it('should proxy to the standard url', function() {
  493. return this.request
  494. .calledWith({
  495. jar: this.jar,
  496. method: this.req.method,
  497. url: `${this.settings.apis.clsi.url}${this.url}`,
  498. timeout: 60 * 1000
  499. })
  500. .should.equal(true)
  501. })
  502. })
  503. describe('user with build parameter via query string', function() {
  504. beforeEach(function() {
  505. this.CompileManager.getProjectCompileLimits = sinon
  506. .stub()
  507. .callsArgWith(1, null, { compileGroup: 'standard' })
  508. this.req.query = { build: 1234 }
  509. return this.CompileController.proxyToClsi(
  510. this.project_id,
  511. (this.url = '/test'),
  512. this.req,
  513. this.res,
  514. this.next
  515. )
  516. })
  517. it('should proxy to the standard url without the build parameter', function() {
  518. return this.request
  519. .calledWith({
  520. jar: this.jar,
  521. method: this.req.method,
  522. url: `${this.settings.apis.clsi.url}${this.url}`,
  523. timeout: 60 * 1000
  524. })
  525. .should.equal(true)
  526. })
  527. })
  528. })
  529. describe('new pdf viewer', function() {
  530. beforeEach(function() {
  531. return (this.req.query = { pdfng: true })
  532. })
  533. describe('user with standard priority', function() {
  534. beforeEach(function() {
  535. this.CompileManager.getProjectCompileLimits = sinon
  536. .stub()
  537. .callsArgWith(1, null, { compileGroup: 'standard' })
  538. return this.CompileController.proxyToClsi(
  539. this.project_id,
  540. (this.url = '/test'),
  541. this.req,
  542. this.res,
  543. this.next
  544. )
  545. })
  546. it('should open a request to the CLSI', function() {
  547. return this.request
  548. .calledWith({
  549. jar: this.jar,
  550. method: this.req.method,
  551. url: `${this.settings.apis.clsi.url}${this.url}`,
  552. timeout: 60 * 1000,
  553. headers: {
  554. Range: '123-456',
  555. 'If-Range': 'abcdef',
  556. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
  557. }
  558. })
  559. .should.equal(true)
  560. })
  561. it('should pass the request on to the client', function() {
  562. return this.proxy.pipe.calledWith(this.res).should.equal(true)
  563. })
  564. it('should bind an error handle to the request proxy', function() {
  565. return this.proxy.on.calledWith('error').should.equal(true)
  566. })
  567. })
  568. describe('user with build parameter via query string', function() {
  569. beforeEach(function() {
  570. this.CompileManager.getProjectCompileLimits = sinon
  571. .stub()
  572. .callsArgWith(1, null, { compileGroup: 'standard' })
  573. this.req.query = { build: 1234, pdfng: true }
  574. return this.CompileController.proxyToClsi(
  575. this.project_id,
  576. (this.url = '/test'),
  577. this.req,
  578. this.res,
  579. this.next
  580. )
  581. })
  582. it('should proxy to the standard url with the build parameter', function() {
  583. return this.request
  584. .calledWith({
  585. jar: this.jar,
  586. method: this.req.method,
  587. qs: { build: 1234 },
  588. url: `${this.settings.apis.clsi.url}${this.url}`,
  589. timeout: 60 * 1000,
  590. headers: {
  591. Range: '123-456',
  592. 'If-Range': 'abcdef',
  593. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
  594. }
  595. })
  596. .should.equal(true)
  597. })
  598. })
  599. })
  600. })
  601. describe('deleteAuxFiles', function() {
  602. beforeEach(function() {
  603. this.CompileManager.deleteAuxFiles = sinon.stub().callsArg(2)
  604. this.req.params = { Project_id: this.project_id }
  605. this.res.sendStatus = sinon.stub()
  606. return this.CompileController.deleteAuxFiles(
  607. this.req,
  608. this.res,
  609. this.next
  610. )
  611. })
  612. it('should proxy to the CLSI', function() {
  613. return this.CompileManager.deleteAuxFiles
  614. .calledWith(this.project_id)
  615. .should.equal(true)
  616. })
  617. it('should return a 200', function() {
  618. return this.res.sendStatus.calledWith(200).should.equal(true)
  619. })
  620. })
  621. describe('compileAndDownloadPdf', function() {
  622. beforeEach(function() {
  623. this.req = {
  624. params: {
  625. project_id: this.project_id
  626. }
  627. }
  628. this.CompileManager.compile.callsArgWith(3)
  629. this.CompileController.proxyToClsi = sinon.stub()
  630. return (this.res = { send: () => {} })
  631. })
  632. it('should call compile in the compile manager', function(done) {
  633. this.CompileController.compileAndDownloadPdf(this.req, this.res)
  634. this.CompileManager.compile.calledWith(this.project_id).should.equal(true)
  635. return done()
  636. })
  637. it('should proxy the res to the clsi with correct url', function(done) {
  638. this.CompileController.compileAndDownloadPdf(this.req, this.res)
  639. sinon.assert.calledWith(
  640. this.CompileController.proxyToClsi,
  641. this.project_id,
  642. `/project/${this.project_id}/output/output.pdf`,
  643. this.req,
  644. this.res
  645. )
  646. this.CompileController.proxyToClsi
  647. .calledWith(
  648. this.project_id,
  649. `/project/${this.project_id}/output/output.pdf`,
  650. this.req,
  651. this.res
  652. )
  653. .should.equal(true)
  654. return done()
  655. })
  656. })
  657. describe('wordCount', function() {
  658. beforeEach(function() {
  659. this.CompileManager.wordCount = sinon
  660. .stub()
  661. .callsArgWith(3, null, { content: 'body' })
  662. this.req.params = { Project_id: this.project_id }
  663. this.res.send = sinon.stub()
  664. this.res.contentType = sinon.stub()
  665. return this.CompileController.wordCount(this.req, this.res, this.next)
  666. })
  667. it('should proxy to the CLSI', function() {
  668. return this.CompileManager.wordCount
  669. .calledWith(this.project_id, this.user_id, false)
  670. .should.equal(true)
  671. })
  672. it('should return a 200 and body', function() {
  673. return this.res.send.calledWith({ content: 'body' }).should.equal(true)
  674. })
  675. })
  676. })