ProjectRootDocManagerTests.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. /* eslint-disable
  2. max-len,
  3. no-return-assign,
  4. no-unused-vars,
  5. no-useless-escape,
  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 { expect } = require('chai')
  15. const { ObjectId } = require('mongodb')
  16. const sinon = require('sinon')
  17. const modulePath =
  18. '../../../../app/src/Features/Project/ProjectRootDocManager.js'
  19. const SandboxedModule = require('sandboxed-module')
  20. describe('ProjectRootDocManager', function () {
  21. beforeEach(function () {
  22. this.project_id = 'project-123'
  23. this.docPaths = {}
  24. this.docId1 = new ObjectId()
  25. this.docId2 = new ObjectId()
  26. this.docId3 = new ObjectId()
  27. this.docId4 = new ObjectId()
  28. this.docPaths[this.docId1] = '/chapter1.tex'
  29. this.docPaths[this.docId2] = '/main.tex'
  30. this.docPaths[this.docId3] = '/nested/chapter1a.tex'
  31. this.docPaths[this.docId4] = '/nested/chapter1b.tex'
  32. this.sl_req_id = 'sl-req-id-123'
  33. this.callback = sinon.stub()
  34. this.globbyFiles = ['a.tex', 'b.tex', 'main.tex']
  35. this.globby = sinon.stub().returns(
  36. new Promise(resolve => {
  37. return resolve(this.globbyFiles)
  38. })
  39. )
  40. this.fs = {
  41. readFile: sinon.stub().callsArgWith(2, new Error('file not found')),
  42. stat: sinon.stub().callsArgWith(1, null, { size: 100 }),
  43. }
  44. return (this.ProjectRootDocManager = SandboxedModule.require(modulePath, {
  45. requires: {
  46. './ProjectEntityHandler': (this.ProjectEntityHandler = {}),
  47. './ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {}),
  48. './ProjectGetter': (this.ProjectGetter = {}),
  49. globby: this.globby,
  50. fs: this.fs,
  51. },
  52. }))
  53. })
  54. describe('setRootDocAutomatically', function () {
  55. beforeEach(function () {
  56. this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
  57. this.ProjectEntityUpdateHandler.isPathValidForRootDoc = sinon
  58. .stub()
  59. .returns(true)
  60. })
  61. describe('when there is a suitable root doc', function () {
  62. beforeEach(function (done) {
  63. this.docs = {
  64. '/chapter1.tex': {
  65. _id: this.docId1,
  66. lines: [
  67. 'something else',
  68. '\\begin{document}',
  69. 'Hello world',
  70. '\\end{document}',
  71. ],
  72. },
  73. '/main.tex': {
  74. _id: this.docId2,
  75. lines: [
  76. 'different line',
  77. '\\documentclass{article}',
  78. '\\input{chapter1}',
  79. ],
  80. },
  81. '/nested/chapter1a.tex': {
  82. _id: this.docId3,
  83. lines: ['Hello world'],
  84. },
  85. '/nested/chapter1b.tex': {
  86. _id: this.docId4,
  87. lines: ['Hello world'],
  88. },
  89. }
  90. this.ProjectEntityHandler.getAllDocs = sinon
  91. .stub()
  92. .callsArgWith(1, null, this.docs)
  93. this.ProjectRootDocManager.setRootDocAutomatically(
  94. this.project_id,
  95. done
  96. )
  97. })
  98. it('should check the docs of the project', function () {
  99. return this.ProjectEntityHandler.getAllDocs
  100. .calledWith(this.project_id)
  101. .should.equal(true)
  102. })
  103. it('should set the root doc to the doc containing a documentclass', function () {
  104. return this.ProjectEntityUpdateHandler.setRootDoc
  105. .calledWith(this.project_id, this.docId2)
  106. .should.equal(true)
  107. })
  108. })
  109. describe('when the root doc is an Rtex file', function () {
  110. beforeEach(function (done) {
  111. this.docs = {
  112. '/chapter1.tex': {
  113. _id: this.docId1,
  114. lines: ['\\begin{document}', 'Hello world', '\\end{document}'],
  115. },
  116. '/main.Rtex': {
  117. _id: this.docId2,
  118. lines: ['\\documentclass{article}', '\\input{chapter1}'],
  119. },
  120. }
  121. this.ProjectEntityHandler.getAllDocs = sinon
  122. .stub()
  123. .callsArgWith(1, null, this.docs)
  124. return this.ProjectRootDocManager.setRootDocAutomatically(
  125. this.project_id,
  126. done
  127. )
  128. })
  129. it('should set the root doc to the doc containing a documentclass', function () {
  130. return this.ProjectEntityUpdateHandler.setRootDoc
  131. .calledWith(this.project_id, this.docId2)
  132. .should.equal(true)
  133. })
  134. })
  135. describe('when there is no suitable root doc', function () {
  136. beforeEach(function (done) {
  137. this.docs = {
  138. '/chapter1.tex': {
  139. _id: this.docId1,
  140. lines: ['\\begin{document}', 'Hello world', '\\end{document}'],
  141. },
  142. '/style.bst': {
  143. _id: this.docId2,
  144. lines: ['%Example: \\documentclass{article}'],
  145. },
  146. }
  147. this.ProjectEntityHandler.getAllDocs = sinon
  148. .stub()
  149. .callsArgWith(1, null, this.docs)
  150. return this.ProjectRootDocManager.setRootDocAutomatically(
  151. this.project_id,
  152. done
  153. )
  154. })
  155. it('should not set the root doc to the doc containing a documentclass', function () {
  156. return this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(
  157. false
  158. )
  159. })
  160. })
  161. })
  162. describe('findRootDocFileFromDirectory', function () {
  163. beforeEach(function () {
  164. this.fs.readFile
  165. .withArgs('/foo/a.tex')
  166. .callsArgWith(2, null, 'Hello World!')
  167. this.fs.readFile
  168. .withArgs('/foo/b.tex')
  169. .callsArgWith(2, null, "I'm a little teapot, get me out of here.")
  170. this.fs.readFile
  171. .withArgs('/foo/main.tex')
  172. .callsArgWith(2, null, "Help, I'm trapped in a unit testing factory")
  173. this.fs.readFile
  174. .withArgs('/foo/c.tex')
  175. .callsArgWith(2, null, 'Tomato, tomahto.')
  176. this.fs.readFile
  177. .withArgs('/foo/a/a.tex')
  178. .callsArgWith(2, null, 'Potato? Potahto. Potootee!')
  179. return (this.documentclassContent = '% test\n\\documentclass\n% test')
  180. })
  181. describe('when there is a file in a subfolder', function () {
  182. beforeEach(function () {
  183. // have to splice globbyFiles weirdly because of the way the stubbed globby method handles references
  184. return this.globbyFiles.splice(
  185. 0,
  186. this.globbyFiles.length,
  187. 'c.tex',
  188. 'a.tex',
  189. 'a/a.tex',
  190. 'b.tex'
  191. )
  192. })
  193. it('processes the root folder files first, and then the subfolder, in alphabetical order', function (done) {
  194. return this.ProjectRootDocManager.findRootDocFileFromDirectory(
  195. '/foo',
  196. (error, path) => {
  197. expect(error).not.to.exist
  198. expect(path).not.to.exist
  199. sinon.assert.callOrder(
  200. this.fs.readFile.withArgs('/foo/a.tex'),
  201. this.fs.readFile.withArgs('/foo/b.tex'),
  202. this.fs.readFile.withArgs('/foo/c.tex'),
  203. this.fs.readFile.withArgs('/foo/a/a.tex')
  204. )
  205. return done()
  206. }
  207. )
  208. })
  209. it('processes smaller files first', function (done) {
  210. this.fs.stat.withArgs('/foo/c.tex').callsArgWith(1, null, { size: 1 })
  211. return this.ProjectRootDocManager.findRootDocFileFromDirectory(
  212. '/foo',
  213. (error, path) => {
  214. expect(error).not.to.exist
  215. expect(path).not.to.exist
  216. sinon.assert.callOrder(
  217. this.fs.readFile.withArgs('/foo/c.tex'),
  218. this.fs.readFile.withArgs('/foo/a.tex'),
  219. this.fs.readFile.withArgs('/foo/b.tex'),
  220. this.fs.readFile.withArgs('/foo/a/a.tex')
  221. )
  222. return done()
  223. }
  224. )
  225. })
  226. })
  227. describe('when main.tex contains a documentclass', function () {
  228. beforeEach(function () {
  229. return this.fs.readFile
  230. .withArgs('/foo/main.tex')
  231. .callsArgWith(2, null, this.documentclassContent)
  232. })
  233. it('returns main.tex', function (done) {
  234. return this.ProjectRootDocManager.findRootDocFileFromDirectory(
  235. '/foo',
  236. (error, path, content) => {
  237. expect(error).not.to.exist
  238. expect(path).to.equal('main.tex')
  239. expect(content).to.equal(this.documentclassContent)
  240. return done()
  241. }
  242. )
  243. })
  244. it('processes main.text first and stops processing when it finds the content', function (done) {
  245. return this.ProjectRootDocManager.findRootDocFileFromDirectory(
  246. '/foo',
  247. () => {
  248. expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
  249. expect(this.fs.readFile).not.to.be.calledWith('/foo/a.tex')
  250. return done()
  251. }
  252. )
  253. })
  254. })
  255. describe('when a.tex contains a documentclass', function () {
  256. beforeEach(function () {
  257. return this.fs.readFile
  258. .withArgs('/foo/a.tex')
  259. .callsArgWith(2, null, this.documentclassContent)
  260. })
  261. it('returns a.tex', function (done) {
  262. return this.ProjectRootDocManager.findRootDocFileFromDirectory(
  263. '/foo',
  264. (error, path, content) => {
  265. expect(error).not.to.exist
  266. expect(path).to.equal('a.tex')
  267. expect(content).to.equal(this.documentclassContent)
  268. return done()
  269. }
  270. )
  271. })
  272. it('processes main.text first and stops processing when it finds the content', function (done) {
  273. return this.ProjectRootDocManager.findRootDocFileFromDirectory(
  274. '/foo',
  275. () => {
  276. expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
  277. expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
  278. expect(this.fs.readFile).not.to.be.calledWith('/foo/b.tex')
  279. return done()
  280. }
  281. )
  282. })
  283. })
  284. describe('when there is no documentclass', function () {
  285. it('returns null with no error', function (done) {
  286. return this.ProjectRootDocManager.findRootDocFileFromDirectory(
  287. '/foo',
  288. (error, path, content) => {
  289. expect(error).not.to.exist
  290. expect(path).not.to.exist
  291. expect(content).not.to.exist
  292. return done()
  293. }
  294. )
  295. })
  296. it('processes all the files', function (done) {
  297. return this.ProjectRootDocManager.findRootDocFileFromDirectory(
  298. '/foo',
  299. () => {
  300. expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
  301. expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
  302. expect(this.fs.readFile).to.be.calledWith('/foo/b.tex')
  303. return done()
  304. }
  305. )
  306. })
  307. })
  308. describe('when there is an error reading a file', function () {
  309. beforeEach(function () {
  310. return this.fs.readFile
  311. .withArgs('/foo/a.tex')
  312. .callsArgWith(2, new Error('something went wrong'))
  313. })
  314. it('returns an error', function (done) {
  315. return this.ProjectRootDocManager.findRootDocFileFromDirectory(
  316. '/foo',
  317. (error, path, content) => {
  318. expect(error).to.exist
  319. expect(path).not.to.exist
  320. expect(content).not.to.exist
  321. return done()
  322. }
  323. )
  324. })
  325. })
  326. })
  327. describe('setRootDocFromName', function () {
  328. describe('when there is a suitable root doc', function () {
  329. beforeEach(function (done) {
  330. this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
  331. .stub()
  332. .callsArgWith(1, null, this.docPaths)
  333. this.ProjectEntityUpdateHandler.setRootDoc = sinon
  334. .stub()
  335. .callsArgWith(2)
  336. return this.ProjectRootDocManager.setRootDocFromName(
  337. this.project_id,
  338. '/main.tex',
  339. done
  340. )
  341. })
  342. it('should check the docs of the project', function () {
  343. return this.ProjectEntityHandler.getAllDocPathsFromProjectById
  344. .calledWith(this.project_id)
  345. .should.equal(true)
  346. })
  347. it('should set the root doc to main.tex', function () {
  348. return this.ProjectEntityUpdateHandler.setRootDoc
  349. .calledWith(this.project_id, this.docId2.toString())
  350. .should.equal(true)
  351. })
  352. })
  353. describe('when there is a suitable root doc but the leading slash is missing', function () {
  354. beforeEach(function (done) {
  355. this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
  356. .stub()
  357. .callsArgWith(1, null, this.docPaths)
  358. this.ProjectEntityUpdateHandler.setRootDoc = sinon
  359. .stub()
  360. .callsArgWith(2)
  361. return this.ProjectRootDocManager.setRootDocFromName(
  362. this.project_id,
  363. 'main.tex',
  364. done
  365. )
  366. })
  367. it('should check the docs of the project', function () {
  368. return this.ProjectEntityHandler.getAllDocPathsFromProjectById
  369. .calledWith(this.project_id)
  370. .should.equal(true)
  371. })
  372. it('should set the root doc to main.tex', function () {
  373. return this.ProjectEntityUpdateHandler.setRootDoc
  374. .calledWith(this.project_id, this.docId2.toString())
  375. .should.equal(true)
  376. })
  377. })
  378. describe('when there is a suitable root doc with a basename match', function () {
  379. beforeEach(function (done) {
  380. this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
  381. .stub()
  382. .callsArgWith(1, null, this.docPaths)
  383. this.ProjectEntityUpdateHandler.setRootDoc = sinon
  384. .stub()
  385. .callsArgWith(2)
  386. return this.ProjectRootDocManager.setRootDocFromName(
  387. this.project_id,
  388. 'chapter1a.tex',
  389. done
  390. )
  391. })
  392. it('should check the docs of the project', function () {
  393. return this.ProjectEntityHandler.getAllDocPathsFromProjectById
  394. .calledWith(this.project_id)
  395. .should.equal(true)
  396. })
  397. it('should set the root doc using the basename', function () {
  398. return this.ProjectEntityUpdateHandler.setRootDoc
  399. .calledWith(this.project_id, this.docId3.toString())
  400. .should.equal(true)
  401. })
  402. })
  403. describe('when there is a suitable root doc but the filename is in quotes', function () {
  404. beforeEach(function (done) {
  405. this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
  406. .stub()
  407. .callsArgWith(1, null, this.docPaths)
  408. this.ProjectEntityUpdateHandler.setRootDoc = sinon
  409. .stub()
  410. .callsArgWith(2)
  411. return this.ProjectRootDocManager.setRootDocFromName(
  412. this.project_id,
  413. "'main.tex'",
  414. done
  415. )
  416. })
  417. it('should check the docs of the project', function () {
  418. return this.ProjectEntityHandler.getAllDocPathsFromProjectById
  419. .calledWith(this.project_id)
  420. .should.equal(true)
  421. })
  422. it('should set the root doc to main.tex', function () {
  423. return this.ProjectEntityUpdateHandler.setRootDoc
  424. .calledWith(this.project_id, this.docId2.toString())
  425. .should.equal(true)
  426. })
  427. })
  428. describe('when there is no suitable root doc', function () {
  429. beforeEach(function (done) {
  430. this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
  431. .stub()
  432. .callsArgWith(1, null, this.docPaths)
  433. this.ProjectEntityUpdateHandler.setRootDoc = sinon
  434. .stub()
  435. .callsArgWith(2)
  436. return this.ProjectRootDocManager.setRootDocFromName(
  437. this.project_id,
  438. 'other.tex',
  439. done
  440. )
  441. })
  442. it('should not set the root doc', function () {
  443. return this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(
  444. false
  445. )
  446. })
  447. })
  448. })
  449. describe('ensureRootDocumentIsSet', function () {
  450. beforeEach(function () {
  451. this.project = {}
  452. this.ProjectGetter.getProject = sinon
  453. .stub()
  454. .callsArgWith(2, null, this.project)
  455. return (this.ProjectRootDocManager.setRootDocAutomatically = sinon
  456. .stub()
  457. .callsArgWith(1, null))
  458. })
  459. describe('when the root doc is set', function () {
  460. beforeEach(function () {
  461. this.project.rootDoc_id = this.docId2
  462. return this.ProjectRootDocManager.ensureRootDocumentIsSet(
  463. this.project_id,
  464. this.callback
  465. )
  466. })
  467. it('should find the project fetching only the rootDoc_id field', function () {
  468. return this.ProjectGetter.getProject
  469. .calledWith(this.project_id, { rootDoc_id: 1 })
  470. .should.equal(true)
  471. })
  472. it('should not try to update the project rootDoc_id', function () {
  473. return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
  474. false
  475. )
  476. })
  477. it('should call the callback', function () {
  478. return this.callback.called.should.equal(true)
  479. })
  480. })
  481. describe('when the root doc is not set', function () {
  482. beforeEach(function () {
  483. return this.ProjectRootDocManager.ensureRootDocumentIsSet(
  484. this.project_id,
  485. this.callback
  486. )
  487. })
  488. it('should find the project with only the rootDoc_id field', function () {
  489. return this.ProjectGetter.getProject
  490. .calledWith(this.project_id, { rootDoc_id: 1 })
  491. .should.equal(true)
  492. })
  493. it('should update the project rootDoc_id', function () {
  494. return this.ProjectRootDocManager.setRootDocAutomatically
  495. .calledWith(this.project_id)
  496. .should.equal(true)
  497. })
  498. it('should call the callback', function () {
  499. return this.callback.called.should.equal(true)
  500. })
  501. })
  502. describe('when the project does not exist', function () {
  503. beforeEach(function () {
  504. this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
  505. return this.ProjectRootDocManager.ensureRootDocumentIsSet(
  506. this.project_id,
  507. this.callback
  508. )
  509. })
  510. it('should call the callback with an error', function () {
  511. return this.callback
  512. .calledWith(
  513. sinon.match
  514. .instanceOf(Error)
  515. .and(sinon.match.has('message', 'project not found'))
  516. )
  517. .should.equal(true)
  518. })
  519. })
  520. })
  521. describe('ensureRootDocumentIsValid', function () {
  522. beforeEach(function () {
  523. this.project = {}
  524. this.ProjectGetter.getProject = sinon
  525. .stub()
  526. .callsArgWith(2, null, this.project)
  527. this.ProjectGetter.getProjectWithoutDocLines = sinon
  528. .stub()
  529. .callsArgWith(1, null, this.project)
  530. this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().yields()
  531. this.ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
  532. this.ProjectRootDocManager.setRootDocAutomatically = sinon
  533. .stub()
  534. .callsArgWith(1, null)
  535. })
  536. describe('when the root doc is set', function () {
  537. describe('when the root doc is valid', function () {
  538. beforeEach(function () {
  539. this.project.rootDoc_id = this.docId2
  540. this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
  541. .stub()
  542. .callsArgWith(2, null, this.docPaths[this.docId2])
  543. return this.ProjectRootDocManager.ensureRootDocumentIsValid(
  544. this.project_id,
  545. this.callback
  546. )
  547. })
  548. it('should find the project without doc lines', function () {
  549. return this.ProjectGetter.getProjectWithoutDocLines
  550. .calledWith(this.project_id)
  551. .should.equal(true)
  552. })
  553. it('should not try to update the project rootDoc_id', function () {
  554. return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
  555. false
  556. )
  557. })
  558. it('should call the callback', function () {
  559. return this.callback.called.should.equal(true)
  560. })
  561. })
  562. describe('when the root doc is not valid', function () {
  563. beforeEach(function () {
  564. this.project.rootDoc_id = new ObjectId()
  565. this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
  566. .stub()
  567. .callsArgWith(2, null, null)
  568. return this.ProjectRootDocManager.ensureRootDocumentIsValid(
  569. this.project_id,
  570. this.callback
  571. )
  572. })
  573. it('should find the project without doc lines', function () {
  574. return this.ProjectGetter.getProjectWithoutDocLines
  575. .calledWith(this.project_id)
  576. .should.equal(true)
  577. })
  578. it('should unset the root doc', function () {
  579. return this.ProjectEntityUpdateHandler.unsetRootDoc
  580. .calledWith(this.project_id)
  581. .should.equal(true)
  582. })
  583. it('should try to find a new rootDoc', function () {
  584. return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
  585. true
  586. )
  587. })
  588. it('should call the callback', function () {
  589. return this.callback.called.should.equal(true)
  590. })
  591. })
  592. })
  593. describe('when the root doc is not set', function () {
  594. beforeEach(function () {
  595. return this.ProjectRootDocManager.ensureRootDocumentIsValid(
  596. this.project_id,
  597. this.callback
  598. )
  599. })
  600. it('should find the project without doc lines', function () {
  601. return this.ProjectGetter.getProjectWithoutDocLines
  602. .calledWith(this.project_id)
  603. .should.equal(true)
  604. })
  605. it('should update the project rootDoc_id', function () {
  606. return this.ProjectRootDocManager.setRootDocAutomatically
  607. .calledWith(this.project_id)
  608. .should.equal(true)
  609. })
  610. it('should call the callback', function () {
  611. return this.callback.called.should.equal(true)
  612. })
  613. })
  614. describe('when the project does not exist', function () {
  615. beforeEach(function () {
  616. this.ProjectGetter.getProjectWithoutDocLines = sinon
  617. .stub()
  618. .callsArgWith(1, null, null)
  619. return this.ProjectRootDocManager.ensureRootDocumentIsValid(
  620. this.project_id,
  621. this.callback
  622. )
  623. })
  624. it('should call the callback with an error', function () {
  625. return this.callback
  626. .calledWith(
  627. sinon.match
  628. .instanceOf(Error)
  629. .and(sinon.match.has('message', 'project not found'))
  630. )
  631. .should.equal(true)
  632. })
  633. })
  634. })
  635. })