From 43a80ef8a5034dceb8bfbb41251b179a32c8594d Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Mon, 27 Oct 2025 09:19:08 +0000 Subject: [PATCH] Merge pull request #28945 from overleaf/mj-tear-down-server-side-referencing [web] Tear down server side referencing GitOrigin-RevId: 37feac39cc7bf219a2cbc463191163534434f267 --- .../LinkedFiles/LinkedFilesController.mjs | 18 +- .../References/ReferencesController.mjs | 55 +-- .../Features/References/ReferencesHandler.mjs | 214 --------- .../LinkedFilesController.test.mjs | 43 +- .../References/ReferencesController.test.mjs | 83 +--- .../src/References/ReferencesHandler.test.mjs | 444 ------------------ 6 files changed, 51 insertions(+), 806 deletions(-) delete mode 100644 services/web/app/src/Features/References/ReferencesHandler.mjs delete mode 100644 services/web/test/unit/src/References/ReferencesHandler.test.mjs diff --git a/services/web/app/src/Features/LinkedFiles/LinkedFilesController.mjs b/services/web/app/src/Features/LinkedFiles/LinkedFilesController.mjs index fb7436a546..70e53dd743 100644 --- a/services/web/app/src/Features/LinkedFiles/LinkedFilesController.mjs +++ b/services/web/app/src/Features/LinkedFiles/LinkedFilesController.mjs @@ -19,12 +19,10 @@ import LinkedFilesErrors from './LinkedFilesErrors.mjs' import { OutputFileFetchFailedError, FileTooLargeError, - OError, } from '../Errors/Errors.js' import Modules from '../../infrastructure/Modules.js' import { plainTextResponse } from '../../infrastructure/Response.js' import { z, zz, validateReq } from '../../infrastructure/Validation.js' -import ReferencesHandler from '../References/ReferencesHandler.mjs' import EditorRealTimeController from '../Editor/EditorRealTimeController.js' import { expressify } from '@overleaf/promise-utils' import ProjectOutputFileAgent from './ProjectOutputFileAgent.mjs' @@ -142,26 +140,16 @@ async function refreshLinkedFile(req, res, next) { } if (req.body.shouldReindexReferences) { - let data - try { - data = await ReferencesHandler.promises.indexAll(projectId) - } catch (error) { - OError.tag(error, 'failed to index references', { - projectId, - }) - return next(error) - } + // Signal to clients that they should re-index references EditorRealTimeController.emitToRoom( projectId, 'references:keys:updated', - data.keys, + [], true, clientId ) - res.json({ new_file_id: newFileId }) - } else { - res.json({ new_file_id: newFileId }) } + res.json({ new_file_id: newFileId }) } export default LinkedFilesController = { diff --git a/services/web/app/src/Features/References/ReferencesController.mjs b/services/web/app/src/Features/References/ReferencesController.mjs index 14dbaec1eb..457c5b70e4 100644 --- a/services/web/app/src/Features/References/ReferencesController.mjs +++ b/services/web/app/src/Features/References/ReferencesController.mjs @@ -1,63 +1,20 @@ -/* eslint-disable - max-len, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -import ReferencesHandler from './ReferencesHandler.mjs' import EditorRealTimeController from '../Editor/EditorRealTimeController.js' -import { OError } from '../Errors/Errors.js' -let ReferencesController - -export default ReferencesController = { +export default { indexAll(req, res, next) { const projectId = req.params.Project_id const { shouldBroadcast, clientId } = req.body - return ReferencesHandler.indexAll(projectId, function (error, data) { - if (error) { - OError.tag(error, 'failed to index references', { projectId }) - return next(error) - } - return ReferencesController._handleIndexResponse( - req, - res, - projectId, - shouldBroadcast, - true, - data, - clientId - ) - }) - }, - - _handleIndexResponse( - req, - res, - projectId, - shouldBroadcast, - isAllDocs, - data, - clientId - ) { - if (data == null || data.keys == null) { - return res.json({ projectId, keys: [] }) - } + // We've migrated to client side indexing, so we only use the message for + // broadcasting that the clients need to re-index. if (shouldBroadcast) { EditorRealTimeController.emitToRoom( projectId, 'references:keys:updated', - data.keys, - isAllDocs, + [], + true, clientId ) } - return res.json(data) + res.json({ projectId, keys: [] }) }, } diff --git a/services/web/app/src/Features/References/ReferencesHandler.mjs b/services/web/app/src/Features/References/ReferencesHandler.mjs deleted file mode 100644 index b550fa1d0d..0000000000 --- a/services/web/app/src/Features/References/ReferencesHandler.mjs +++ /dev/null @@ -1,214 +0,0 @@ -/* eslint-disable - n/handle-callback-err, - max-len, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS103: Rewrite code to no longer use __guard__ - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -import OError from '@overleaf/o-error' -import logger from '@overleaf/logger' -import request from 'request' -import settings from '@overleaf/settings' -import Features from '../../infrastructure/Features.js' -import ProjectGetter from '../Project/ProjectGetter.mjs' -import UserGetter from '../User/UserGetter.js' -import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.mjs' -import _ from 'lodash' -import Async from 'async' -import Errors from '../Errors/Errors.js' -import { promisify } from '@overleaf/promise-utils' - -let ReferencesHandler - -if (!Features.hasFeature('references')) { - logger.debug('references search not enabled') -} - -export default ReferencesHandler = { - _buildDocUrl(projectId, docId) { - return { - url: `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}/raw`, - } - }, - - _findBibFileRefs(project) { - const fileRefs = [] - function _process(folder) { - _.forEach(folder.fileRefs || [], function (file) { - if ( - __guard__(file != null ? file.name : undefined, x1 => - x1.match(/^.*\.bib$/) - ) - ) { - return fileRefs.push(file) - } - }) - return _.forEach(folder.folders || [], folder => _process(folder)) - } - _.forEach(project.rootFolder || [], rootFolder => _process(rootFolder)) - return fileRefs - }, - - _findBibDocIds(project) { - const ids = [] - function _process(folder) { - _.forEach(folder.docs || [], function (doc) { - if ( - __guard__(doc != null ? doc.name : undefined, x1 => - x1.match(/^.*\.bib$/) - ) - ) { - return ids.push(doc._id) - } - }) - return _.forEach(folder.folders || [], folder => _process(folder)) - } - _.forEach(project.rootFolder || [], rootFolder => _process(rootFolder)) - return ids - }, - - _isFullIndex(project, callback) { - if (callback == null) { - callback = function () {} - } - return UserGetter.getUser( - project.owner_ref, - { features: true }, - function (err, owner) { - if (err != null) { - return callback(err) - } - const features = owner != null ? owner.features : undefined - return callback( - null, - (features != null ? features.references : undefined) === true || - (features != null ? features.referencesSearch : undefined) === true - ) - } - ) - }, - - indexAll(projectId, callback) { - if (callback == null) { - callback = function () {} - } - return ProjectGetter.getProject( - projectId, - { rootFolder: true, owner_ref: 1, 'overleaf.history.id': 1 }, - function (err, project) { - if (err) { - OError.tag(err, 'error finding project', { - projectId, - }) - return callback(err) - } - if (!project) { - return callback( - new Errors.NotFoundError(`project does not exist: ${projectId}`) - ) - } - logger.debug({ projectId }, 'indexing all bib files in project') - const docIds = ReferencesHandler._findBibDocIds(project) - const fileRefs = ReferencesHandler._findBibFileRefs(project) - return ReferencesHandler._doIndexOperation( - projectId, - project, - docIds, - fileRefs, - callback - ) - } - ) - }, - - _doIndexOperation(projectId, project, docIds, fileRefs, callback) { - if (!Features.hasFeature('references')) { - return callback() - } - const historyId = project?.overleaf?.history?.id - if (!historyId) { - return callback( - new OError('project does not have a history id', { projectId }) - ) - } - return ReferencesHandler._isFullIndex(project, function (err, isFullIndex) { - if (err) { - OError.tag(err, 'error checking whether to do full index', { - projectId, - }) - return callback(err) - } - logger.debug( - { projectId, docIds }, - 'flushing docs to mongo before calling references service' - ) - return Async.series( - docIds.map( - docId => cb => - DocumentUpdaterHandler.flushDocToMongo(projectId, docId, cb) - ), - function (err) { - // continue - if (err) { - OError.tag(err, 'error flushing docs to mongo', { - projectId, - docIds, - }) - return callback(err) - } - const bibDocUrls = docIds.map(docId => - ReferencesHandler._buildDocUrl(projectId, docId) - ) - const bibFileUrls = fileRefs.map(fileRef => ({ - url: `${settings.apis.project_history.url}/project/${historyId}/blob/${fileRef.hash}`, - })) - const sourceURLs = bibDocUrls.concat(bibFileUrls) - return request.post( - { - url: `${settings.apis.references.url}/project/${projectId}/index`, - json: { - docUrls: sourceURLs.map(item => item.url), - sourceURLs, - fullIndex: isFullIndex, - }, - }, - function (err, res, data) { - if (err) { - OError.tag(err, 'error communicating with references api', { - projectId, - }) - return callback(err) - } - if (res.statusCode >= 200 && res.statusCode < 300) { - logger.debug({ projectId }, 'got keys from references api') - return callback(null, data) - } else { - err = new Error( - `references api responded with non-success code: ${res.statusCode}` - ) - return callback(err) - } - } - ) - } - ) - }) - }, -} - -ReferencesHandler.promises = { - indexAll: promisify(ReferencesHandler.indexAll), -} - -function __guard__(value, transform) { - return typeof value !== 'undefined' && value !== null - ? transform(value) - : undefined -} diff --git a/services/web/test/unit/src/LinkedFiles/LinkedFilesController.test.mjs b/services/web/test/unit/src/LinkedFiles/LinkedFilesController.test.mjs index f34c654bb4..98e0bf7845 100644 --- a/services/web/test/unit/src/LinkedFiles/LinkedFilesController.test.mjs +++ b/services/web/test/unit/src/LinkedFiles/LinkedFilesController.test.mjs @@ -34,8 +34,7 @@ describe('LinkedFilesController', function () { ctx.SessionManager = { getLoggedInUserId: sinon.stub().returns(ctx.userId), } - ctx.EditorRealTimeController = {} - ctx.ReferencesHandler = {} + ctx.EditorRealTimeController = { emitToRoom: sinon.stub() } ctx.UrlAgent = {} ctx.ProjectFileAgent = {} ctx.ProjectOutputFileAgent = {} @@ -74,13 +73,6 @@ describe('LinkedFilesController', function () { }) ) - vi.doMock( - '../../../../app/src/Features/References/ReferencesHandler', - () => ({ - default: ctx.ReferencesHandler, - }) - ) - vi.doMock('../../../../app/src/Features/LinkedFiles/UrlAgent', () => ({ default: ctx.UrlAgent, })) @@ -200,5 +192,38 @@ describe('LinkedFilesController', function () { ctx.LinkedFilesController.refreshLinkedFile(ctx.req, ctx.res, ctx.next) }) }) + + describe('when bib file re-indexing is required', function () { + const clientId = 'client-id' + beforeEach(function (ctx) { + ctx.req.body.shouldReindexReferences = true + ctx.req.body.clientId = clientId + }) + + it('informs clients to re-index bib references', async function (ctx) { + await new Promise(resolve => { + ctx.next = sinon.stub().callsFake(() => resolve('unexpected error')) + ctx.res = { + json: () => { + expect( + ctx.EditorRealTimeController.emitToRoom + ).to.have.been.calledWith( + ctx.projectId, + 'references:keys:updated', + [], + true, + clientId + ) + resolve() + }, + } + ctx.LinkedFilesController.refreshLinkedFile( + ctx.req, + ctx.res, + ctx.next + ) + }) + }) + }) }) }) diff --git a/services/web/test/unit/src/References/ReferencesController.test.mjs b/services/web/test/unit/src/References/ReferencesController.test.mjs index c578712f45..aaba6ad853 100644 --- a/services/web/test/unit/src/References/ReferencesController.test.mjs +++ b/services/web/test/unit/src/References/ReferencesController.test.mjs @@ -15,16 +15,6 @@ describe('ReferencesController', function () { }), })) - vi.doMock( - '../../../../app/src/Features/References/ReferencesHandler', - () => ({ - default: (ctx.ReferencesHandler = { - index: sinon.stub(), - indexAll: sinon.stub(), - }), - }) - ) - vi.doMock( '../../../../app/src/Features/Editor/EditorRealTimeController', () => ({ @@ -45,16 +35,15 @@ describe('ReferencesController', function () { ctx.res.json = sinon.stub() ctx.res.sendStatus = sinon.stub() ctx.next = sinon.stub() - ctx.fakeResponseData = { + ctx.expectedResponseData = { projectId: ctx.projectId, - keys: ['one', 'two', 'three'], + keys: [], } }) describe('indexAll', function () { beforeEach(function (ctx) { ctx.req.body = { shouldBroadcast: false } - ctx.ReferencesHandler.indexAll.callsArgWith(1, null, ctx.fakeResponseData) ctx.call = callback => { ctx.controller.indexAll(ctx.req, ctx.res, ctx.next) return callback() @@ -72,23 +61,11 @@ describe('ReferencesController', function () { }) }) - it('should return data', async function (ctx) { + it('should return expected empty data', async function (ctx) { await new Promise(resolve => { ctx.call(() => { ctx.res.json.callCount.should.equal(1) - ctx.res.json.calledWith(ctx.fakeResponseData).should.equal(true) - resolve() - }) - }) - }) - - it('should call ReferencesHandler.indexAll', async function (ctx) { - await new Promise(resolve => { - ctx.call(() => { - ctx.ReferencesHandler.indexAll.callCount.should.equal(1) - ctx.ReferencesHandler.indexAll - .calledWith(ctx.projectId) - .should.equal(true) + ctx.res.json.calledWith(ctx.expectedResponseData).should.equal(true) resolve() }) }) @@ -96,7 +73,6 @@ describe('ReferencesController', function () { describe('when shouldBroadcast is true', function () { beforeEach(function (ctx) { - ctx.ReferencesHandler.index.callsArgWith(2, null, ctx.fakeResponseData) ctx.req.body.shouldBroadcast = true }) @@ -120,11 +96,11 @@ describe('ReferencesController', function () { }) }) - it('should still return data', async function (ctx) { + it('should still return empty data', async function (ctx) { await new Promise(resolve => { ctx.call(() => { ctx.res.json.callCount.should.equal(1) - ctx.res.json.calledWith(ctx.fakeResponseData).should.equal(true) + ctx.res.json.calledWith(ctx.expectedResponseData).should.equal(true) resolve() }) }) @@ -133,7 +109,6 @@ describe('ReferencesController', function () { describe('when shouldBroadcast is false', function () { beforeEach(function (ctx) { - ctx.ReferencesHandler.index.callsArgWith(2, null, ctx.fakeResponseData) ctx.req.body.shouldBroadcast = false }) @@ -157,57 +132,15 @@ describe('ReferencesController', function () { }) }) - it('should still return data', async function (ctx) { + it('should still return empty data', async function (ctx) { await new Promise(resolve => { ctx.call(() => { ctx.res.json.callCount.should.equal(1) - ctx.res.json.calledWith(ctx.fakeResponseData).should.equal(true) + ctx.res.json.calledWith(ctx.expectedResponseData).should.equal(true) resolve() }) }) }) }) }) - - describe('there is no data', function () { - beforeEach(function (ctx) { - ctx.ReferencesHandler.indexAll.callsArgWith(1) - ctx.call = callback => { - ctx.controller.indexAll(ctx.req, ctx.res, ctx.next) - callback() - } - }) - - it('should not call EditorRealTimeController.emitToRoom', async function (ctx) { - await new Promise(resolve => { - ctx.call(() => { - ctx.EditorRealTimeController.emitToRoom.callCount.should.equal(0) - resolve() - }) - }) - }) - - it('should not produce an error', async function (ctx) { - await new Promise(resolve => { - ctx.call(() => { - ctx.res.sendStatus.callCount.should.equal(0) - ctx.res.sendStatus.calledWith(500).should.equal(false) - ctx.res.sendStatus.calledWith(400).should.equal(false) - resolve() - }) - }) - }) - - it('should send a response with an empty keys list', async function (ctx) { - await new Promise(resolve => { - ctx.call(() => { - ctx.res.json.called.should.equal(true) - ctx.res.json - .calledWith({ projectId: ctx.projectId, keys: [] }) - .should.equal(true) - resolve() - }) - }) - }) - }) }) diff --git a/services/web/test/unit/src/References/ReferencesHandler.test.mjs b/services/web/test/unit/src/References/ReferencesHandler.test.mjs deleted file mode 100644 index 2fa80052fc..0000000000 --- a/services/web/test/unit/src/References/ReferencesHandler.test.mjs +++ /dev/null @@ -1,444 +0,0 @@ -import { expect, vi } from 'vitest' -import sinon from 'sinon' -import Errors from '../../../../app/src/Features/Errors/Errors.js' -const modulePath = - '../../../../app/src/Features/References/ReferencesHandler.mjs' - -vi.mock('../../../../app/src/Features/Errors/Errors.js', () => - vi.importActual('../../../../app/src/Features/Errors/Errors.js') -) - -describe('ReferencesHandler', function () { - beforeEach(async function (ctx) { - ctx.projectId = '222' - ctx.historyId = 42 - ctx.fakeProject = { - _id: ctx.projectId, - owner_ref: (ctx.fakeOwner = { - _id: 'some_owner', - features: { - references: false, - }, - }), - rootFolder: [ - { - docs: [ - { name: 'one.bib', _id: 'aaa' }, - { name: 'two.txt', _id: 'bbb' }, - ], - folders: [ - { - docs: [{ name: 'three.bib', _id: 'ccc' }], - fileRefs: [ - { name: 'four.bib', _id: 'fff', hash: 'abc' }, - { name: 'five.bib', _id: 'ggg', hash: 'def' }, - ], - folders: [], - }, - ], - }, - ], - overleaf: { history: { id: ctx.historyId } }, - } - ctx.docIds = ['aaa', 'ccc'] - - vi.doMock('@overleaf/settings', () => ({ - default: (ctx.settings = { - apis: { - references: { url: 'http://some.url/references' }, - docstore: { url: 'http://some.url/docstore' }, - filestore: { url: 'http://some.url/filestore' }, - project_history: { url: 'http://project-history.local' }, - }, - }), - })) - - vi.doMock('request', () => ({ - default: (ctx.request = { - get: sinon.stub(), - post: sinon.stub(), - }), - })) - - vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({ - default: (ctx.ProjectGetter = { - getProject: sinon.stub().callsArgWith(2, null, ctx.fakeProject), - }), - })) - - vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({ - default: (ctx.UserGetter = { - getUser: sinon.stub(), - }), - })) - - vi.doMock( - '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler', - () => ({ - default: (ctx.DocumentUpdaterHandler = { - flushDocToMongo: sinon.stub().callsArgWith(2, null), - }), - }) - ) - - vi.doMock('../../../../app/src/infrastructure/Features', () => ({ - default: (ctx.Features = { - hasFeature: sinon.stub().returns(true), - }), - })) - - ctx.handler = (await import(modulePath)).default - ctx.fakeResponseData = { - projectId: ctx.projectId, - keys: ['k1', 'k2'], - } - }) - - describe('indexAll', function () { - beforeEach(function (ctx) { - sinon.stub(ctx.handler, '_findBibDocIds').returns(['aaa', 'ccc']) - sinon.stub(ctx.handler, '_findBibFileRefs').returns([ - { _id: 'fff', hash: 'abc' }, - { _id: 'ggg', hash: 'def' }, - ]) - sinon.stub(ctx.handler, '_isFullIndex').callsArgWith(1, null, true) - ctx.request.post.callsArgWith( - 1, - null, - { statusCode: 200 }, - ctx.fakeResponseData - ) - return (ctx.call = callback => { - return ctx.handler.indexAll(ctx.projectId, callback) - }) - }) - - it('should call _findBibDocIds', async function (ctx) { - await new Promise(resolve => { - return ctx.call((err, data) => { - expect(err).to.be.null - ctx.handler._findBibDocIds.callCount.should.equal(1) - ctx.handler._findBibDocIds - .calledWith(ctx.fakeProject) - .should.equal(true) - return resolve() - }) - }) - }) - - it('should call _findBibFileRefs', async function (ctx) { - await new Promise(resolve => { - return ctx.call((err, data) => { - expect(err).to.be.null - ctx.handler._findBibDocIds.callCount.should.equal(1) - ctx.handler._findBibDocIds - .calledWith(ctx.fakeProject) - .should.equal(true) - return resolve() - }) - }) - }) - - it('should call DocumentUpdaterHandler.flushDocToMongo', async function (ctx) { - await new Promise(resolve => { - return ctx.call((err, data) => { - expect(err).to.be.null - ctx.DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal(2) - return resolve() - }) - }) - }) - - it('should make a request to references service', async function (ctx) { - await new Promise(resolve => { - return ctx.call((err, data) => { - expect(err).to.be.null - ctx.request.post.callCount.should.equal(1) - const arg = ctx.request.post.firstCall.args[0] - expect(arg.json).to.have.all.keys( - 'docUrls', - 'sourceURLs', - 'fullIndex' - ) - expect(arg.json.docUrls.length).to.equal(4) - expect(arg.json.docUrls).to.deep.equal([ - `${ctx.settings.apis.docstore.url}/project/${ctx.projectId}/doc/aaa/raw`, - `${ctx.settings.apis.docstore.url}/project/${ctx.projectId}/doc/ccc/raw`, - `${ctx.settings.apis.project_history.url}/project/${ctx.historyId}/blob/abc`, - `${ctx.settings.apis.project_history.url}/project/${ctx.historyId}/blob/def`, - ]) - expect(arg.json.sourceURLs.length).to.equal(4) - expect(arg.json.sourceURLs).to.deep.equal([ - { - url: `${ctx.settings.apis.docstore.url}/project/${ctx.projectId}/doc/aaa/raw`, - }, - { - url: `${ctx.settings.apis.docstore.url}/project/${ctx.projectId}/doc/ccc/raw`, - }, - { - url: `${ctx.settings.apis.project_history.url}/project/${ctx.historyId}/blob/abc`, - }, - { - url: `${ctx.settings.apis.project_history.url}/project/${ctx.historyId}/blob/def`, - }, - ]) - expect(arg.json.fullIndex).to.equal(true) - return resolve() - }) - }) - }) - - it('should not produce an error', async function (ctx) { - await new Promise(resolve => { - return ctx.call((err, data) => { - expect(err).to.equal(null) - return resolve() - }) - }) - }) - - it('should return data', async function (ctx) { - await new Promise(resolve => { - return ctx.call((err, data) => { - expect(err).to.be.null - expect(data).to.not.equal(null) - expect(data).to.not.equal(undefined) - expect(data).to.equal(ctx.fakeResponseData) - return resolve() - }) - }) - }) - - describe('when ProjectGetter.getProject produces an error', function () { - beforeEach(function (ctx) { - ctx.ProjectGetter.getProject.callsArgWith(2, new Error('woops')) - }) - - it('should produce an error', async function (ctx) { - await new Promise(resolve => { - ctx.call((err, data) => { - expect(err).to.not.equal(null) - expect(err).to.be.instanceof(Error) - expect(data).to.equal(undefined) - resolve() - }) - }) - }) - - it('should not send request', async function (ctx) { - await new Promise(resolve => { - ctx.call(() => { - ctx.request.post.callCount.should.equal(0) - resolve() - }) - }) - }) - }) - - describe('when ProjectGetter.getProject returns null', function () { - beforeEach(function (ctx) { - ctx.ProjectGetter.getProject.callsArgWith(2, null) - }) - - it('should produce an error', async function (ctx) { - await new Promise(resolve => { - ctx.call((err, data) => { - expect(err).to.not.equal(null) - expect(err).to.be.instanceof(Errors.NotFoundError) - expect(data).to.equal(undefined) - resolve() - }) - }) - }) - - it('should not send request', async function (ctx) { - await new Promise(resolve => { - ctx.call(() => { - ctx.request.post.callCount.should.equal(0) - resolve() - }) - }) - }) - }) - - describe('when _isFullIndex produces an error', function () { - beforeEach(function (ctx) { - ctx.ProjectGetter.getProject.callsArgWith(2, null, ctx.fakeProject) - ctx.handler._isFullIndex.callsArgWith(1, new Error('woops')) - }) - - it('should produce an error', async function (ctx) { - await new Promise(resolve => { - ctx.call((err, data) => { - expect(err).to.not.equal(null) - expect(err).to.be.instanceof(Error) - expect(data).to.equal(undefined) - resolve() - }) - }) - }) - - it('should not send request', async function (ctx) { - await new Promise(resolve => { - ctx.call(() => { - ctx.request.post.callCount.should.equal(0) - resolve() - }) - }) - }) - }) - - describe('when flushDocToMongo produces an error', function () { - beforeEach(function (ctx) { - ctx.ProjectGetter.getProject.callsArgWith(2, null, ctx.fakeProject) - ctx.handler._isFullIndex.callsArgWith(1, false) - ctx.DocumentUpdaterHandler.flushDocToMongo.callsArgWith( - 2, - new Error('woops') - ) - }) - - it('should produce an error', async function (ctx) { - await new Promise(resolve => { - ctx.call((err, data) => { - expect(err).to.not.equal(null) - expect(err).to.be.instanceof(Error) - expect(data).to.equal(undefined) - resolve() - }) - }) - }) - - it('should not send request', async function (ctx) { - await new Promise(resolve => { - ctx.call(() => { - ctx.request.post.callCount.should.equal(0) - resolve() - }) - }) - }) - }) - }) - - describe('_findBibDocIds', function () { - beforeEach(function (ctx) { - ctx.fakeProject = { - rootFolder: [ - { - docs: [ - { name: 'one.bib', _id: 'aaa' }, - { name: 'two.txt', _id: 'bbb' }, - ], - folders: [ - { docs: [{ name: 'three.bib', _id: 'ccc' }], folders: [] }, - ], - }, - ], - } - ctx.expectedIds = ['aaa', 'ccc'] - }) - - it('should select the correct docIds', function (ctx) { - const result = ctx.handler._findBibDocIds(ctx.fakeProject) - expect(result).to.deep.equal(ctx.expectedIds) - }) - - it('should not error with a non array of folders from dirty data', function (ctx) { - ctx.fakeProject.rootFolder[0].folders[0].folders = {} - const result = ctx.handler._findBibDocIds(ctx.fakeProject) - expect(result).to.deep.equal(ctx.expectedIds) - }) - }) - - describe('_findBibFileRefs', function () { - beforeEach(function (ctx) { - ctx.fakeProject = { - rootFolder: [ - { - docs: [ - { name: 'one.bib', _id: 'aaa' }, - { name: 'two.txt', _id: 'bbb' }, - ], - fileRefs: [{ name: 'other.bib', _id: 'ddd' }], - folders: [ - { - docs: [{ name: 'three.bib', _id: 'ccc' }], - fileRefs: [{ name: 'four.bib', _id: 'ghg' }], - folders: [], - }, - ], - }, - ], - } - ctx.expectedIds = [ - ctx.fakeProject.rootFolder[0].fileRefs[0], - ctx.fakeProject.rootFolder[0].folders[0].fileRefs[0], - ] - }) - - it('should select the correct docIds', function (ctx) { - const result = ctx.handler._findBibFileRefs(ctx.fakeProject) - expect(result).to.deep.equal(ctx.expectedIds) - }) - }) - - describe('_isFullIndex', function () { - beforeEach(function (ctx) { - ctx.fakeProject = { owner_ref: (ctx.owner_ref = 'owner-ref-123') } - ctx.owner = { - features: { - references: false, - }, - } - ctx.UserGetter.getUser = sinon.stub() - ctx.UserGetter.getUser - .withArgs(ctx.owner_ref, { features: true }) - .yields(null, ctx.owner) - ctx.call = callback => { - ctx.handler._isFullIndex(ctx.fakeProject, callback) - } - }) - - describe('with references feature on', function () { - beforeEach(function (ctx) { - ctx.owner.features.references = true - }) - - it('should return true', function (ctx) { - ctx.call((err, isFullIndex) => { - expect(err).to.equal(null) - expect(isFullIndex).to.equal(true) - }) - }) - }) - - describe('with references feature off', function () { - beforeEach(function (ctx) { - ctx.owner.features.references = false - }) - - it('should return false', function (ctx) { - ctx.call((err, isFullIndex) => { - expect(err).to.equal(null) - expect(isFullIndex).to.equal(false) - }) - }) - }) - - describe('with referencesSearch', function () { - beforeEach(function (ctx) { - ctx.owner.features = { - referencesSearch: true, - references: false, - } - }) - - it('should return true', function (ctx) { - ctx.call((err, isFullIndex) => { - expect(err).to.equal(null) - expect(isFullIndex).to.equal(true) - }) - }) - }) - }) -})