ReferencesHandlerTests.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. /* eslint-disable
  2. n/handle-callback-err,
  3. max-len,
  4. mocha/no-identical-title,
  5. no-return-assign,
  6. no-unused-vars,
  7. */
  8. // TODO: This file was created by bulk-decaffeinate.
  9. // Fix any style issues and re-enable lint.
  10. /*
  11. * decaffeinate suggestions:
  12. * DS102: Remove unnecessary code created because of implicit returns
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const SandboxedModule = require('sandboxed-module')
  16. const { assert, expect } = require('chai')
  17. const sinon = require('sinon')
  18. const Errors = require('../../../../app/src/Features/Errors/Errors')
  19. const modulePath = '../../../../app/src/Features/References/ReferencesHandler'
  20. describe('ReferencesHandler', function () {
  21. beforeEach(function () {
  22. this.projectId = '222'
  23. this.fakeProject = {
  24. _id: this.projectId,
  25. owner_ref: (this.fakeOwner = {
  26. _id: 'some_owner',
  27. features: {
  28. references: false,
  29. },
  30. }),
  31. rootFolder: [
  32. {
  33. docs: [
  34. { name: 'one.bib', _id: 'aaa' },
  35. { name: 'two.txt', _id: 'bbb' },
  36. ],
  37. folders: [
  38. {
  39. docs: [{ name: 'three.bib', _id: 'ccc' }],
  40. fileRefs: [{ name: 'four.bib', _id: 'ghg' }],
  41. folders: [],
  42. },
  43. ],
  44. },
  45. ],
  46. }
  47. this.docIds = ['aaa', 'ccc']
  48. this.handler = SandboxedModule.require(modulePath, {
  49. requires: {
  50. '@overleaf/settings': (this.settings = {
  51. apis: {
  52. references: { url: 'http://some.url/references' },
  53. docstore: { url: 'http://some.url/docstore' },
  54. filestore: { url: 'http://some.url/filestore' },
  55. },
  56. }),
  57. request: (this.request = {
  58. get: sinon.stub(),
  59. post: sinon.stub(),
  60. }),
  61. '../Project/ProjectGetter': (this.ProjectGetter = {
  62. getProject: sinon.stub().callsArgWith(2, null, this.fakeProject),
  63. }),
  64. '../User/UserGetter': (this.UserGetter = {
  65. getUser: sinon.stub(),
  66. }),
  67. '../DocumentUpdater/DocumentUpdaterHandler':
  68. (this.DocumentUpdaterHandler = {
  69. flushDocToMongo: sinon.stub().callsArgWith(2, null),
  70. }),
  71. '../../infrastructure/Features': (this.Features = {
  72. hasFeature: sinon.stub().returns(true),
  73. }),
  74. },
  75. })
  76. return (this.fakeResponseData = {
  77. projectId: this.projectId,
  78. keys: ['k1', 'k2'],
  79. })
  80. })
  81. describe('index', function () {
  82. beforeEach(function () {
  83. sinon.stub(this.handler, '_findBibDocIds')
  84. sinon.stub(this.handler, '_findBibFileIds')
  85. sinon.stub(this.handler, '_isFullIndex').callsArgWith(1, null, true)
  86. this.request.post.callsArgWith(
  87. 1,
  88. null,
  89. { statusCode: 200 },
  90. this.fakeResponseData
  91. )
  92. return (this.call = callback => {
  93. return this.handler.index(this.projectId, this.docIds, callback)
  94. })
  95. })
  96. describe('when references feature is disabled', function () {
  97. beforeEach(function () {
  98. this.Features.hasFeature.withArgs('references').returns(false)
  99. })
  100. it('should not try to retrieve any user information', function (done) {
  101. this.call(() => {
  102. this.UserGetter.getUser.callCount.should.equal(0)
  103. done()
  104. })
  105. })
  106. it('should not produce an error', function (done) {
  107. return this.call(err => {
  108. expect(err).to.equal(undefined)
  109. return done()
  110. })
  111. })
  112. })
  113. describe('with docIds as an array', function () {
  114. beforeEach(function () {
  115. return (this.docIds = ['aaa', 'ccc'])
  116. })
  117. it('should not call _findBibDocIds', function (done) {
  118. return this.call((err, data) => {
  119. this.handler._findBibDocIds.callCount.should.equal(0)
  120. return done()
  121. })
  122. })
  123. it('should call ProjectGetter.getProject', function (done) {
  124. return this.call((err, data) => {
  125. this.ProjectGetter.getProject.callCount.should.equal(1)
  126. this.ProjectGetter.getProject
  127. .calledWith(this.projectId)
  128. .should.equal(true)
  129. return done()
  130. })
  131. })
  132. it('should not call _findBibDocIds', function (done) {
  133. return this.call((err, data) => {
  134. this.handler._findBibDocIds.callCount.should.equal(0)
  135. return done()
  136. })
  137. })
  138. it('should call DocumentUpdaterHandler.flushDocToMongo', function (done) {
  139. return this.call((err, data) => {
  140. this.DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal(2)
  141. this.docIds.forEach(docId => {
  142. return this.DocumentUpdaterHandler.flushDocToMongo
  143. .calledWith(this.projectId, docId)
  144. .should.equal(true)
  145. })
  146. return done()
  147. })
  148. })
  149. it('should make a request to references service', function (done) {
  150. return this.call((err, data) => {
  151. this.request.post.callCount.should.equal(1)
  152. const arg = this.request.post.firstCall.args[0]
  153. expect(arg.json).to.have.all.keys('docUrls', 'fullIndex')
  154. expect(arg.json.docUrls.length).to.equal(2)
  155. expect(arg.json.fullIndex).to.equal(true)
  156. return done()
  157. })
  158. })
  159. it('should not produce an error', function (done) {
  160. return this.call((err, data) => {
  161. expect(err).to.equal(null)
  162. return done()
  163. })
  164. })
  165. it('should return data', function (done) {
  166. return this.call((err, data) => {
  167. expect(data).to.not.equal(null)
  168. expect(data).to.not.equal(undefined)
  169. expect(data).to.equal(this.fakeResponseData)
  170. return done()
  171. })
  172. })
  173. })
  174. describe('when ProjectGetter.getProject produces an error', function () {
  175. beforeEach(function () {
  176. return this.ProjectGetter.getProject.callsArgWith(2, new Error('woops'))
  177. })
  178. it('should produce an error', function (done) {
  179. return this.call((err, data) => {
  180. expect(err).to.not.equal(null)
  181. expect(err).to.be.instanceof(Error)
  182. expect(data).to.equal(undefined)
  183. return done()
  184. })
  185. })
  186. it('should not send request', function (done) {
  187. return this.call((err, data) => {
  188. this.request.post.callCount.should.equal(0)
  189. return done()
  190. })
  191. })
  192. })
  193. describe('when ProjectGetter.getProject returns null', function () {
  194. beforeEach(function () {
  195. return this.ProjectGetter.getProject.callsArgWith(2, null)
  196. })
  197. it('should produce an error', function (done) {
  198. return this.call((err, data) => {
  199. expect(err).to.not.equal(null)
  200. expect(err).to.be.instanceof(Errors.NotFoundError)
  201. expect(data).to.equal(undefined)
  202. return done()
  203. })
  204. })
  205. it('should not send request', function (done) {
  206. return this.call((err, data) => {
  207. this.request.post.callCount.should.equal(0)
  208. return done()
  209. })
  210. })
  211. })
  212. describe('when _isFullIndex produces an error', function () {
  213. beforeEach(function () {
  214. this.ProjectGetter.getProject.callsArgWith(2, null, this.fakeProject)
  215. return this.handler._isFullIndex.callsArgWith(1, new Error('woops'))
  216. })
  217. it('should produce an error', function (done) {
  218. return this.call((err, data) => {
  219. expect(err).to.not.equal(null)
  220. expect(err).to.be.instanceof(Error)
  221. expect(data).to.equal(undefined)
  222. return done()
  223. })
  224. })
  225. it('should not send request', function (done) {
  226. return this.call((err, data) => {
  227. this.request.post.callCount.should.equal(0)
  228. return done()
  229. })
  230. })
  231. })
  232. describe('when flushDocToMongo produces an error', function () {
  233. beforeEach(function () {
  234. this.ProjectGetter.getProject.callsArgWith(2, null, this.fakeProject)
  235. this.handler._isFullIndex.callsArgWith(1, false)
  236. return this.DocumentUpdaterHandler.flushDocToMongo.callsArgWith(
  237. 2,
  238. new Error('woops')
  239. )
  240. })
  241. it('should produce an error', function (done) {
  242. return this.call((err, data) => {
  243. expect(err).to.not.equal(null)
  244. expect(err).to.be.instanceof(Error)
  245. expect(data).to.equal(undefined)
  246. return done()
  247. })
  248. })
  249. it('should not send request', function (done) {
  250. return this.call((err, data) => {
  251. this.request.post.callCount.should.equal(0)
  252. return done()
  253. })
  254. })
  255. })
  256. describe('when request produces an error', function () {
  257. beforeEach(function () {
  258. this.ProjectGetter.getProject.callsArgWith(2, null, this.fakeProject)
  259. this.handler._isFullIndex.callsArgWith(1, null, false)
  260. this.DocumentUpdaterHandler.flushDocToMongo.callsArgWith(2, null)
  261. return this.request.post.callsArgWith(1, new Error('woops'))
  262. })
  263. it('should produce an error', function (done) {
  264. return this.call((err, data) => {
  265. expect(err).to.not.equal(null)
  266. expect(err).to.be.instanceof(Error)
  267. expect(data).to.equal(undefined)
  268. return done()
  269. })
  270. })
  271. })
  272. describe('when request responds with error status', function () {
  273. beforeEach(function () {
  274. this.ProjectGetter.getProject.callsArgWith(2, null, this.fakeProject)
  275. this.handler._isFullIndex.callsArgWith(1, null, false)
  276. return this.request.post.callsArgWith(
  277. 1,
  278. null,
  279. { statusCode: 500 },
  280. null
  281. )
  282. })
  283. it('should produce an error', function (done) {
  284. return this.call((err, data) => {
  285. expect(err).to.not.equal(null)
  286. expect(err).to.be.instanceof(Error)
  287. expect(data).to.equal(undefined)
  288. return done()
  289. })
  290. })
  291. })
  292. })
  293. describe('indexAll', function () {
  294. beforeEach(function () {
  295. sinon.stub(this.handler, '_findBibDocIds').returns(['aaa', 'ccc'])
  296. sinon.stub(this.handler, '_findBibFileIds').returns(['fff', 'ggg'])
  297. sinon.stub(this.handler, '_isFullIndex').callsArgWith(1, null, true)
  298. this.request.post.callsArgWith(
  299. 1,
  300. null,
  301. { statusCode: 200 },
  302. this.fakeResponseData
  303. )
  304. return (this.call = callback => {
  305. return this.handler.indexAll(this.projectId, callback)
  306. })
  307. })
  308. it('should call _findBibDocIds', function (done) {
  309. return this.call((err, data) => {
  310. this.handler._findBibDocIds.callCount.should.equal(1)
  311. this.handler._findBibDocIds
  312. .calledWith(this.fakeProject)
  313. .should.equal(true)
  314. return done()
  315. })
  316. })
  317. it('should call _findBibFileIds', function (done) {
  318. return this.call((err, data) => {
  319. this.handler._findBibDocIds.callCount.should.equal(1)
  320. this.handler._findBibDocIds
  321. .calledWith(this.fakeProject)
  322. .should.equal(true)
  323. return done()
  324. })
  325. })
  326. it('should call DocumentUpdaterHandler.flushDocToMongo', function (done) {
  327. return this.call((err, data) => {
  328. this.DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal(2)
  329. return done()
  330. })
  331. })
  332. it('should make a request to references service', function (done) {
  333. return this.call((err, data) => {
  334. this.request.post.callCount.should.equal(1)
  335. const arg = this.request.post.firstCall.args[0]
  336. expect(arg.json).to.have.all.keys('docUrls', 'fullIndex')
  337. expect(arg.json.docUrls.length).to.equal(4)
  338. expect(arg.json.fullIndex).to.equal(true)
  339. return done()
  340. })
  341. })
  342. it('should not produce an error', function (done) {
  343. return this.call((err, data) => {
  344. expect(err).to.equal(null)
  345. return done()
  346. })
  347. })
  348. it('should return data', function (done) {
  349. return this.call((err, data) => {
  350. expect(data).to.not.equal(null)
  351. expect(data).to.not.equal(undefined)
  352. expect(data).to.equal(this.fakeResponseData)
  353. return done()
  354. })
  355. })
  356. describe('when ProjectGetter.getProject produces an error', function () {
  357. beforeEach(function () {
  358. return this.ProjectGetter.getProject.callsArgWith(2, new Error('woops'))
  359. })
  360. it('should produce an error', function (done) {
  361. return this.call((err, data) => {
  362. expect(err).to.not.equal(null)
  363. expect(err).to.be.instanceof(Error)
  364. expect(data).to.equal(undefined)
  365. return done()
  366. })
  367. })
  368. it('should not send request', function (done) {
  369. return this.call((err, data) => {
  370. this.request.post.callCount.should.equal(0)
  371. return done()
  372. })
  373. })
  374. })
  375. describe('when ProjectGetter.getProject returns null', function () {
  376. beforeEach(function () {
  377. return this.ProjectGetter.getProject.callsArgWith(2, null)
  378. })
  379. it('should produce an error', function (done) {
  380. return this.call((err, data) => {
  381. expect(err).to.not.equal(null)
  382. expect(err).to.be.instanceof(Errors.NotFoundError)
  383. expect(data).to.equal(undefined)
  384. return done()
  385. })
  386. })
  387. it('should not send request', function (done) {
  388. return this.call((err, data) => {
  389. this.request.post.callCount.should.equal(0)
  390. return done()
  391. })
  392. })
  393. })
  394. describe('when _isFullIndex produces an error', function () {
  395. beforeEach(function () {
  396. this.ProjectGetter.getProject.callsArgWith(2, null, this.fakeProject)
  397. return this.handler._isFullIndex.callsArgWith(1, new Error('woops'))
  398. })
  399. it('should produce an error', function (done) {
  400. return this.call((err, data) => {
  401. expect(err).to.not.equal(null)
  402. expect(err).to.be.instanceof(Error)
  403. expect(data).to.equal(undefined)
  404. return done()
  405. })
  406. })
  407. it('should not send request', function (done) {
  408. return this.call((err, data) => {
  409. this.request.post.callCount.should.equal(0)
  410. return done()
  411. })
  412. })
  413. })
  414. describe('when flushDocToMongo produces an error', function () {
  415. beforeEach(function () {
  416. this.ProjectGetter.getProject.callsArgWith(2, null, this.fakeProject)
  417. this.handler._isFullIndex.callsArgWith(1, false)
  418. return this.DocumentUpdaterHandler.flushDocToMongo.callsArgWith(
  419. 2,
  420. new Error('woops')
  421. )
  422. })
  423. it('should produce an error', function (done) {
  424. return this.call((err, data) => {
  425. expect(err).to.not.equal(null)
  426. expect(err).to.be.instanceof(Error)
  427. expect(data).to.equal(undefined)
  428. return done()
  429. })
  430. })
  431. it('should not send request', function (done) {
  432. return this.call((err, data) => {
  433. this.request.post.callCount.should.equal(0)
  434. return done()
  435. })
  436. })
  437. })
  438. })
  439. describe('_findBibDocIds', function () {
  440. beforeEach(function () {
  441. this.fakeProject = {
  442. rootFolder: [
  443. {
  444. docs: [
  445. { name: 'one.bib', _id: 'aaa' },
  446. { name: 'two.txt', _id: 'bbb' },
  447. ],
  448. folders: [
  449. { docs: [{ name: 'three.bib', _id: 'ccc' }], folders: [] },
  450. ],
  451. },
  452. ],
  453. }
  454. return (this.expectedIds = ['aaa', 'ccc'])
  455. })
  456. it('should select the correct docIds', function () {
  457. const result = this.handler._findBibDocIds(this.fakeProject)
  458. return expect(result).to.deep.equal(this.expectedIds)
  459. })
  460. it('should not error with a non array of folders from dirty data', function () {
  461. this.fakeProject.rootFolder[0].folders[0].folders = {}
  462. const result = this.handler._findBibDocIds(this.fakeProject)
  463. return expect(result).to.deep.equal(this.expectedIds)
  464. })
  465. })
  466. describe('_findBibFileIds', function () {
  467. beforeEach(function () {
  468. this.fakeProject = {
  469. rootFolder: [
  470. {
  471. docs: [
  472. { name: 'one.bib', _id: 'aaa' },
  473. { name: 'two.txt', _id: 'bbb' },
  474. ],
  475. fileRefs: [{ name: 'other.bib', _id: 'ddd' }],
  476. folders: [
  477. {
  478. docs: [{ name: 'three.bib', _id: 'ccc' }],
  479. fileRefs: [{ name: 'four.bib', _id: 'ghg' }],
  480. folders: [],
  481. },
  482. ],
  483. },
  484. ],
  485. }
  486. return (this.expectedIds = ['ddd', 'ghg'])
  487. })
  488. it('should select the correct docIds', function () {
  489. const result = this.handler._findBibFileIds(this.fakeProject)
  490. return expect(result).to.deep.equal(this.expectedIds)
  491. })
  492. })
  493. describe('_isFullIndex', function () {
  494. beforeEach(function () {
  495. this.fakeProject = { owner_ref: (this.owner_ref = 'owner-ref-123') }
  496. this.owner = {
  497. features: {
  498. references: false,
  499. },
  500. }
  501. this.UserGetter.getUser = sinon.stub()
  502. this.UserGetter.getUser
  503. .withArgs(this.owner_ref, { features: true })
  504. .yields(null, this.owner)
  505. return (this.call = callback => {
  506. return this.handler._isFullIndex(this.fakeProject, callback)
  507. })
  508. })
  509. describe('with references feature on', function () {
  510. beforeEach(function () {
  511. return (this.owner.features.references = true)
  512. })
  513. it('should return true', function () {
  514. return this.call((err, isFullIndex) => {
  515. expect(err).to.equal(null)
  516. return expect(isFullIndex).to.equal(true)
  517. })
  518. })
  519. })
  520. describe('with references feature off', function () {
  521. beforeEach(function () {
  522. return (this.owner.features.references = false)
  523. })
  524. it('should return false', function () {
  525. return this.call((err, isFullIndex) => {
  526. expect(err).to.equal(null)
  527. return expect(isFullIndex).to.equal(false)
  528. })
  529. })
  530. })
  531. describe('with referencesSearch', function () {
  532. beforeEach(function () {
  533. return (this.owner.features = {
  534. referencesSearch: true,
  535. references: false,
  536. })
  537. })
  538. it('should return true', function () {
  539. return this.call((err, isFullIndex) => {
  540. expect(err).to.equal(null)
  541. return expect(isFullIndex).to.equal(true)
  542. })
  543. })
  544. })
  545. })
  546. })