const { expect } = require('chai') const sinon = require('sinon') const SandboxedModule = require('sandboxed-module') const { ObjectId } = require('mongodb') const { Project } = require('../helpers/models/Project') const MODULE_PATH = '../../../../app/src/Features/ThirdPartyDataStore/TpdsProjectFlusher' describe('TpdsProjectFlusher', function() { beforeEach(function() { this.project = { _id: ObjectId() } this.docs = { '/doc/one': { _id: 'mock-doc-1', lines: ['one'], rev: 5 }, '/doc/two': { _id: 'mock-doc-2', lines: ['two'], rev: 6 } } this.files = { '/file/one': { _id: 'mock-file-1', rev: 7 }, '/file/two': { _id: 'mock-file-2', rev: 8 } } this.DocumentUpdaterHandler = { promises: { flushProjectToMongo: sinon.stub().resolves() } } this.ProjectGetter = { promises: { getProject: sinon.stub().resolves(this.project) } } this.ProjectEntityHandler = { promises: { getAllDocs: sinon .stub() .withArgs(this.project._id) .resolves(this.docs), getAllFiles: sinon .stub() .withArgs(this.project._id) .resolves(this.files) } } this.TpdsUpdateSender = { promises: { addDoc: sinon.stub().resolves(), addFile: sinon.stub().resolves() } } this.ProjectMock = sinon.mock(Project) this.TpdsProjectFlusher = SandboxedModule.require(MODULE_PATH, { requires: { '../DocumentUpdater/DocumentUpdaterHandler': this .DocumentUpdaterHandler, '../Project/ProjectGetter': this.ProjectGetter, '../Project/ProjectEntityHandler': this.ProjectEntityHandler, '../../models/Project': { Project }, './TpdsUpdateSender': this.TpdsUpdateSender } }) }) afterEach(function() { this.ProjectMock.restore() }) describe('flushProjectToTpds', function() { describe('usually', function() { beforeEach(async function() { await this.TpdsProjectFlusher.promises.flushProjectToTpds( this.project._id ) }) it('should flush the project from the doc updater', function() { expect( this.DocumentUpdaterHandler.promises.flushProjectToMongo ).to.have.been.calledWith(this.project._id) }) it('should flush each doc to the TPDS', function() { for (const [path, doc] of Object.entries(this.docs)) { expect(this.TpdsUpdateSender.promises.addDoc).to.have.been.calledWith( { project_id: this.project._id, doc_id: doc._id, project_name: this.project.name, rev: doc.rev, path } ) } }) it('should flush each file to the TPDS', function() { for (const [path, file] of Object.entries(this.files)) { expect( this.TpdsUpdateSender.promises.addFile ).to.have.been.calledWith({ project_id: this.project._id, file_id: file._id, project_name: this.project.name, rev: file.rev, path }) } }) }) describe('when a TPDS flush is pending', function() { beforeEach(async function() { this.project.deferredTpdsFlushCounter = 2 this.ProjectMock.expects('updateOne') .withArgs( { _id: this.project._id, deferredTpdsFlushCounter: { $lte: 2 } }, { $set: { deferredTpdsFlushCounter: 0 } } ) .chain('exec') .resolves() await this.TpdsProjectFlusher.promises.flushProjectToTpds( this.project._id ) }) it('resets the deferred flush counter', function() { this.ProjectMock.verify() }) }) }) describe('deferProjectFlushToTpds', function() { beforeEach(async function() { this.ProjectMock.expects('updateOne') .withArgs( { _id: this.project._id }, { $inc: { deferredTpdsFlushCounter: 1 } } ) .chain('exec') .resolves() await this.TpdsProjectFlusher.promises.deferProjectFlushToTpds( this.project._id ) }) it('increments the deferred flush counter', function() { this.ProjectMock.verify() }) }) describe('flushProjectToTpdsIfNeeded', function() { let cases = [0, undefined] cases.forEach(counterValue => { describe(`when the deferred flush counter is ${counterValue}`, function() { beforeEach(async function() { this.project.deferredTpdsFlushCounter = counterValue await this.TpdsProjectFlusher.promises.flushProjectToTpdsIfNeeded( this.project._id ) }) it("doesn't flush the project from the doc updater", function() { expect(this.DocumentUpdaterHandler.promises.flushProjectToMongo).not .to.have.been.called }) it("doesn't flush any doc", function() { expect(this.TpdsUpdateSender.promises.addDoc).not.to.have.been.called }) it("doesn't flush any file", function() { expect(this.TpdsUpdateSender.promises.addFile).not.to.have.been.called }) }) }) cases = [1, 2] cases.forEach(counterValue => { describe(`when the deferred flush counter is ${counterValue}`, function() { beforeEach(async function() { this.project.deferredTpdsFlushCounter = counterValue this.ProjectMock.expects('updateOne') .withArgs( { _id: this.project._id, deferredTpdsFlushCounter: { $lte: counterValue } }, { $set: { deferredTpdsFlushCounter: 0 } } ) .chain('exec') .resolves() await this.TpdsProjectFlusher.promises.flushProjectToTpdsIfNeeded( this.project._id ) }) it('flushes the project from the doc updater', function() { expect( this.DocumentUpdaterHandler.promises.flushProjectToMongo ).to.have.been.calledWith(this.project._id) }) it('flushes each doc to the TPDS', function() { for (const [path, doc] of Object.entries(this.docs)) { expect( this.TpdsUpdateSender.promises.addDoc ).to.have.been.calledWith({ project_id: this.project._id, doc_id: doc._id, project_name: this.project.name, rev: doc.rev, path }) } }) it('flushes each file to the TPDS', function() { for (const [path, file] of Object.entries(this.files)) { expect( this.TpdsUpdateSender.promises.addFile ).to.have.been.calledWith({ project_id: this.project._id, file_id: file._id, project_name: this.project.name, rev: file.rev, path }) } }) it('resets the deferred flush counter', function() { this.ProjectMock.verify() }) }) }) }) })