| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339 |
- const sinon = require('sinon')
- const modulePath = '../../../../app/js/HttpController.js'
- const SandboxedModule = require('sandboxed-module')
- const Errors = require('../../../../app/js/Errors.js')
- describe('HttpController', function () {
- beforeEach(function () {
- this.HttpController = SandboxedModule.require(modulePath, {
- requires: {
- './DocumentManager': (this.DocumentManager = {}),
- './HistoryManager': (this.HistoryManager = {
- flushProjectChangesAsync: sinon.stub(),
- }),
- './ProjectHistoryRedisManager': (this.ProjectHistoryRedisManager = {}),
- './ProjectManager': (this.ProjectManager = {}),
- './DeleteQueueManager': (this.DeleteQueueManager = {}),
- './RedisManager': (this.RedisManager = {
- DOC_OPS_TTL: 42,
- }),
- './Metrics': (this.Metrics = {}),
- './Errors': Errors,
- './Utils': (this.Utils = {
- addTrackedDeletesToContent: sinon.stub().returnsArg(0),
- }),
- './HistoryConversions': (this.HistoryConversions = {
- toHistoryRanges: sinon.stub().returnsArg(0),
- }),
- '@overleaf/settings': { max_doc_length: 2 * 1024 * 1024 },
- },
- })
- this.Metrics.Timer = class Timer {}
- this.Metrics.Timer.prototype.done = sinon.stub()
- this.project_id = 'project-id-123'
- this.projectHistoryId = '123'
- this.doc_id = 'doc-id-123'
- this.source = 'editor'
- this.next = sinon.stub()
- this.res = {
- send: sinon.stub(),
- sendStatus: sinon.stub(),
- json: sinon.stub(),
- }
- })
- describe('getDoc', function () {
- beforeEach(function () {
- this.lines = ['one', 'two', 'three']
- this.ops = ['mock-op-1', 'mock-op-2']
- this.version = 42
- this.fromVersion = 42
- this.ranges = { changes: 'mock', comments: 'mock' }
- this.pathname = '/a/b/c'
- this.req = {
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- },
- query: {},
- body: {},
- }
- })
- describe('when the document exists and no recent ops are requested', function () {
- beforeEach(function () {
- this.DocumentManager.getDocAndRecentOpsWithLock = sinon
- .stub()
- .callsArgWith(
- 3,
- null,
- this.lines,
- this.version,
- [],
- this.ranges,
- this.pathname,
- this.projectHistoryId,
- 'sharejs-text-ot'
- )
- this.HttpController.getDoc(this.req, this.res, this.next)
- })
- it('should get the doc', function () {
- this.DocumentManager.getDocAndRecentOpsWithLock
- .calledWith(this.project_id, this.doc_id, -1)
- .should.equal(true)
- })
- it('should return the doc as JSON', function () {
- this.res.json.should.have.been.calledWith({
- id: this.doc_id,
- lines: this.lines,
- version: this.version,
- ops: [],
- ranges: this.ranges,
- pathname: this.pathname,
- ttlInS: 42,
- type: 'sharejs-text-ot',
- })
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- {
- docId: this.doc_id,
- projectId: this.project_id,
- historyRanges: false,
- },
- 'getting doc via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when recent ops are requested', function () {
- beforeEach(function () {
- this.DocumentManager.getDocAndRecentOpsWithLock = sinon
- .stub()
- .callsArgWith(
- 3,
- null,
- this.lines,
- this.version,
- this.ops,
- this.ranges,
- this.pathname,
- this.projectHistoryId,
- 'sharejs-text-ot'
- )
- this.req.query = { fromVersion: `${this.fromVersion}` }
- this.HttpController.getDoc(this.req, this.res, this.next)
- })
- it('should get the doc', function () {
- this.DocumentManager.getDocAndRecentOpsWithLock
- .calledWith(this.project_id, this.doc_id, this.fromVersion)
- .should.equal(true)
- })
- it('should return the doc as JSON', function () {
- this.res.json.should.have.been.calledWith({
- id: this.doc_id,
- lines: this.lines,
- version: this.version,
- ops: this.ops,
- ranges: this.ranges,
- pathname: this.pathname,
- ttlInS: 42,
- type: 'sharejs-text-ot',
- })
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- {
- docId: this.doc_id,
- projectId: this.project_id,
- historyRanges: false,
- },
- 'getting doc via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when historyRanges query param is true', function () {
- beforeEach(function () {
- this.DocumentManager.getDocAndRecentOpsWithLock = sinon
- .stub()
- .callsArgWith(
- 3,
- null,
- this.lines,
- this.version,
- [],
- this.ranges,
- this.pathname,
- this.projectHistoryId,
- 'sharejs-text-ot'
- )
- this.req.query = { historyRanges: 'true' }
- this.HttpController.getDoc(this.req, this.res, this.next)
- })
- it('should get the doc', function () {
- this.DocumentManager.getDocAndRecentOpsWithLock
- .calledWith(this.project_id, this.doc_id, -1)
- .should.equal(true)
- })
- it('should return the doc as JSON with history ranges processing', function () {
- this.res.json.should.have.been.calledWith({
- id: this.doc_id,
- lines: this.lines,
- version: this.version,
- ops: [],
- ranges: this.ranges,
- pathname: this.pathname,
- ttlInS: 42,
- type: 'sharejs-text-ot',
- })
- })
- it('should call addTrackedDeletesToContent for history ranges processing', function () {
- this.Utils.addTrackedDeletesToContent.called.should.equal(true)
- })
- it('should call toHistoryRanges for range conversion', function () {
- this.HistoryConversions.toHistoryRanges.called.should.equal(true)
- })
- it('should log the request with historyRanges: true', function () {
- this.logger.debug
- .calledWith(
- {
- docId: this.doc_id,
- projectId: this.project_id,
- historyRanges: true,
- },
- 'getting doc via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when the document does not exist', function () {
- beforeEach(function () {
- this.DocumentManager.getDocAndRecentOpsWithLock = sinon
- .stub()
- .callsArgWith(3, null, null, null)
- this.HttpController.getDoc(this.req, this.res, this.next)
- })
- it('should call next with NotFoundError', function () {
- this.next
- .calledWith(sinon.match.instanceOf(Errors.NotFoundError))
- .should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.DocumentManager.getDocAndRecentOpsWithLock = sinon
- .stub()
- .callsArgWith(3, new Error('oops'), null, null)
- this.HttpController.getDoc(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('getComment', function () {
- beforeEach(function () {
- this.ranges = {
- changes: 'mock',
- comments: [
- {
- id: 'comment-id-1',
- },
- {
- id: 'comment-id-2',
- },
- ],
- }
- this.req = {
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- comment_id: this.comment_id,
- },
- query: {},
- body: {},
- }
- })
- beforeEach(function () {
- this.DocumentManager.getCommentWithLock = sinon
- .stub()
- .callsArgWith(3, null, this.ranges.comments[0])
- this.HttpController.getComment(this.req, this.res, this.next)
- })
- it('should get the comment', function () {
- this.DocumentManager.getCommentWithLock
- .calledWith(this.project_id, this.doc_id, this.comment_id)
- .should.equal(true)
- })
- it('should return the comment as JSON', function () {
- this.res.json
- .calledWith({
- id: 'comment-id-1',
- })
- .should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- {
- projectId: this.project_id,
- docId: this.doc_id,
- commentId: this.comment_id,
- },
- 'getting comment via http'
- )
- .should.equal(true)
- })
- })
- describe('setDoc', function () {
- beforeEach(function () {
- this.lines = ['one', 'two', 'three']
- this.source = 'dropbox'
- this.user_id = 'user-id-123'
- this.req = {
- headers: {},
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- },
- query: {},
- body: {
- lines: this.lines,
- source: this.source,
- user_id: this.user_id,
- undoing: (this.undoing = true),
- },
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.DocumentManager.setDocWithLock = sinon
- .stub()
- .callsArgWith(7, null, { rev: '123' })
- this.HttpController.setDoc(this.req, this.res, this.next)
- })
- it('should set the doc', function () {
- this.DocumentManager.setDocWithLock
- .calledWith(
- this.project_id,
- this.doc_id,
- this.lines,
- this.source,
- this.user_id,
- this.undoing,
- true
- )
- .should.equal(true)
- })
- it('should return a json response with the document rev from web', function () {
- this.res.json.calledWithMatch({ rev: '123' }).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- {
- docId: this.doc_id,
- projectId: this.project_id,
- lines: this.lines,
- source: this.source,
- userId: this.user_id,
- undoing: this.undoing,
- },
- 'setting doc via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.DocumentManager.setDocWithLock = sinon
- .stub()
- .callsArgWith(7, new Error('oops'))
- this.HttpController.setDoc(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- describe('when the payload is too large', function () {
- beforeEach(function () {
- const lines = []
- for (let _ = 0; _ <= 200000; _++) {
- lines.push('test test test')
- }
- this.req.body.lines = lines
- this.DocumentManager.setDocWithLock = sinon.stub().callsArgWith(6)
- this.HttpController.setDoc(this.req, this.res, this.next)
- })
- it('should send back a 406 response', function () {
- this.res.sendStatus.calledWith(406).should.equal(true)
- })
- it('should not call setDocWithLock', function () {
- this.DocumentManager.setDocWithLock.callCount.should.equal(0)
- })
- })
- })
- describe('flushProject', function () {
- beforeEach(function () {
- this.req = {
- params: {
- project_id: this.project_id,
- },
- query: {},
- body: {},
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.ProjectManager.flushProjectWithLocks = sinon.stub().callsArgWith(1)
- this.HttpController.flushProject(this.req, this.res, this.next)
- })
- it('should flush the project', function () {
- this.ProjectManager.flushProjectWithLocks
- .calledWith(this.project_id)
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- { projectId: this.project_id },
- 'flushing project via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.ProjectManager.flushProjectWithLocks = sinon
- .stub()
- .callsArgWith(1, new Error('oops'))
- this.HttpController.flushProject(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('flushDocIfLoaded', function () {
- beforeEach(function () {
- this.lines = ['one', 'two', 'three']
- this.version = 42
- this.req = {
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- },
- query: {},
- body: {},
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.DocumentManager.flushDocIfLoadedWithLock = sinon
- .stub()
- .callsArgWith(2)
- this.HttpController.flushDocIfLoaded(this.req, this.res, this.next)
- })
- it('should flush the doc', function () {
- this.DocumentManager.flushDocIfLoadedWithLock
- .calledWith(this.project_id, this.doc_id)
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- { docId: this.doc_id, projectId: this.project_id },
- 'flushing doc via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.DocumentManager.flushDocIfLoadedWithLock = sinon
- .stub()
- .callsArgWith(2, new Error('oops'))
- this.HttpController.flushDocIfLoaded(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('deleteDoc', function () {
- beforeEach(function () {
- this.req = {
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- },
- query: {},
- body: {},
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.DocumentManager.flushAndDeleteDocWithLock = sinon
- .stub()
- .callsArgWith(3)
- this.HttpController.deleteDoc(this.req, this.res, this.next)
- })
- it('should flush and delete the doc', function () {
- this.DocumentManager.flushAndDeleteDocWithLock
- .calledWith(this.project_id, this.doc_id, {
- ignoreFlushErrors: false,
- })
- .should.equal(true)
- })
- it('should flush project history', function () {
- this.HistoryManager.flushProjectChangesAsync
- .calledWithExactly(this.project_id)
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- { docId: this.doc_id, projectId: this.project_id },
- 'deleting doc via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('ignoring errors', function () {
- beforeEach(function () {
- this.req.query.ignore_flush_errors = 'true'
- this.DocumentManager.flushAndDeleteDocWithLock = sinon.stub().yields()
- this.HttpController.deleteDoc(this.req, this.res, this.next)
- })
- it('should delete the doc', function () {
- this.DocumentManager.flushAndDeleteDocWithLock
- .calledWith(this.project_id, this.doc_id, { ignoreFlushErrors: true })
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.DocumentManager.flushAndDeleteDocWithLock = sinon
- .stub()
- .callsArgWith(3, new Error('oops'))
- this.HttpController.deleteDoc(this.req, this.res, this.next)
- })
- it('should flush project history', function () {
- this.HistoryManager.flushProjectChangesAsync
- .calledWithExactly(this.project_id)
- .should.equal(true)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('deleteProject', function () {
- beforeEach(function () {
- this.req = {
- params: {
- project_id: this.project_id,
- },
- query: {},
- body: {},
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.ProjectManager.flushAndDeleteProjectWithLocks = sinon
- .stub()
- .callsArgWith(2)
- this.HttpController.deleteProject(this.req, this.res, this.next)
- })
- it('should delete the project', function () {
- this.ProjectManager.flushAndDeleteProjectWithLocks
- .calledWith(this.project_id)
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- { projectId: this.project_id },
- 'deleting project via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('with the background=true option from realtime', function () {
- beforeEach(function () {
- this.ProjectManager.queueFlushAndDeleteProject = sinon
- .stub()
- .callsArgWith(1)
- this.req.query = { background: true, shutdown: true }
- this.HttpController.deleteProject(this.req, this.res, this.next)
- })
- it('should queue the flush and delete', function () {
- this.ProjectManager.queueFlushAndDeleteProject
- .calledWith(this.project_id)
- .should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.ProjectManager.flushAndDeleteProjectWithLocks = sinon
- .stub()
- .callsArgWith(2, new Error('oops'))
- this.HttpController.deleteProject(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('acceptChanges', function () {
- beforeEach(function () {
- this.req = {
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- change_id: (this.change_id = 'mock-change-od-1'),
- },
- query: {},
- body: {},
- }
- })
- describe('successfully with a single change', function () {
- beforeEach(function () {
- this.DocumentManager.acceptChangesWithLock = sinon
- .stub()
- .callsArgWith(3)
- this.HttpController.acceptChanges(this.req, this.res, this.next)
- })
- it('should accept the change', function () {
- this.DocumentManager.acceptChangesWithLock
- .calledWith(this.project_id, this.doc_id, [this.change_id])
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- { projectId: this.project_id, docId: this.doc_id },
- 'accepting 1 changes via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('succesfully with with multiple changes', function () {
- beforeEach(function () {
- this.change_ids = [
- 'mock-change-od-1',
- 'mock-change-od-2',
- 'mock-change-od-3',
- 'mock-change-od-4',
- ]
- this.req.body = { change_ids: this.change_ids }
- this.DocumentManager.acceptChangesWithLock = sinon
- .stub()
- .callsArgWith(3)
- this.HttpController.acceptChanges(this.req, this.res, this.next)
- })
- it('should accept the changes in the body payload', function () {
- this.DocumentManager.acceptChangesWithLock
- .calledWith(this.project_id, this.doc_id, this.change_ids)
- .should.equal(true)
- })
- it('should log the request with the correct number of changes', function () {
- this.logger.debug
- .calledWith(
- { projectId: this.project_id, docId: this.doc_id },
- `accepting ${this.change_ids.length} changes via http`
- )
- .should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.DocumentManager.acceptChangesWithLock = sinon
- .stub()
- .callsArgWith(3, new Error('oops'))
- this.HttpController.acceptChanges(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('resolveComment', function () {
- beforeEach(function () {
- this.user_id = 'user-id-123'
- this.req = {
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- comment_id: (this.comment_id = 'mock-comment-id'),
- },
- query: {},
- body: {
- user_id: this.user_id,
- },
- }
- this.resolved = true
- })
- describe('successfully', function () {
- beforeEach(function (done) {
- this.DocumentManager.updateCommentStateWithLock = sinon
- .stub()
- .callsArgWith(5)
- this.ProjectHistoryRedisManager.queueOps = sinon.stub()
- this.res.sendStatus.callsFake(() => done())
- this.HttpController.resolveComment(this.req, this.res, this.next)
- })
- it('should accept the change', function () {
- this.DocumentManager.updateCommentStateWithLock
- .calledWith(
- this.project_id,
- this.doc_id,
- this.comment_id,
- this.user_id,
- this.resolved
- )
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- {
- projectId: this.project_id,
- docId: this.doc_id,
- commentId: this.comment_id,
- },
- 'resolving comment via http'
- )
- .should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.DocumentManager.updateCommentStateWithLock = sinon
- .stub()
- .callsArgWith(5, new Error('oops'))
- this.HttpController.resolveComment(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('reopenComment', function () {
- beforeEach(function () {
- this.user_id = 'user-id-123'
- this.req = {
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- comment_id: (this.comment_id = 'mock-comment-id'),
- },
- query: {},
- body: {
- user_id: this.user_id,
- },
- }
- this.resolved = false
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.DocumentManager.updateCommentStateWithLock = sinon
- .stub()
- .callsArgWith(5)
- this.ProjectHistoryRedisManager.queueOps = sinon.stub()
- this.HttpController.reopenComment(this.req, this.res, this.next)
- })
- it('should accept the change', function () {
- this.DocumentManager.updateCommentStateWithLock
- .calledWith(
- this.project_id,
- this.doc_id,
- this.comment_id,
- this.user_id,
- this.resolved
- )
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- {
- projectId: this.project_id,
- docId: this.doc_id,
- commentId: this.comment_id,
- },
- 'reopening comment via http'
- )
- .should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.DocumentManager.updateCommentStateWithLock = sinon
- .stub()
- .callsArgWith(5, new Error('oops'))
- this.HttpController.reopenComment(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('deleteComment', function () {
- beforeEach(function () {
- this.user_id = 'user-id-123'
- this.req = {
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- comment_id: (this.comment_id = 'mock-comment-id'),
- },
- query: {},
- body: {
- user_id: this.user_id,
- },
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.DocumentManager.deleteCommentWithLock = sinon
- .stub()
- .callsArgWith(4)
- this.ProjectHistoryRedisManager.queueOps = sinon.stub()
- this.HttpController.deleteComment(this.req, this.res, this.next)
- })
- it('should accept the change', function () {
- this.DocumentManager.deleteCommentWithLock
- .calledWith(
- this.project_id,
- this.doc_id,
- this.comment_id,
- this.user_id
- )
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- {
- projectId: this.project_id,
- docId: this.doc_id,
- commentId: this.comment_id,
- },
- 'deleting comment via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.DocumentManager.deleteCommentWithLock = sinon
- .stub()
- .callsArgWith(4, new Error('oops'))
- this.HttpController.deleteComment(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('getProjectDocsAndFlushIfOld', function () {
- beforeEach(function () {
- this.state = '01234567890abcdef'
- this.docs = [
- { _id: '1234', lines: 'hello', v: 23 },
- { _id: '4567', lines: 'world', v: 45 },
- ]
- this.req = {
- params: {
- project_id: this.project_id,
- },
- query: {
- state: this.state,
- },
- body: {},
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.ProjectManager.getProjectDocsAndFlushIfOld = sinon
- .stub()
- .callsArgWith(3, null, this.docs)
- this.HttpController.getProjectDocsAndFlushIfOld(
- this.req,
- this.res,
- this.next
- )
- })
- it('should get docs from the project manager', function () {
- this.ProjectManager.getProjectDocsAndFlushIfOld
- .calledWith(this.project_id, this.state, {})
- .should.equal(true)
- })
- it('should return a successful response', function () {
- this.res.send.calledWith(this.docs).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- { projectId: this.project_id, exclude: [] },
- 'getting docs via http'
- )
- .should.equal(true)
- })
- it('should log the response', function () {
- this.logger.debug
- .calledWith(
- { projectId: this.project_id, result: ['1234:23', '4567:45'] },
- 'got docs via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when there is a conflict', function () {
- beforeEach(function () {
- this.ProjectManager.getProjectDocsAndFlushIfOld = sinon
- .stub()
- .callsArgWith(
- 3,
- new Errors.ProjectStateChangedError('project state changed')
- )
- this.HttpController.getProjectDocsAndFlushIfOld(
- this.req,
- this.res,
- this.next
- )
- })
- it('should return an HTTP 409 Conflict response', function () {
- this.res.sendStatus.calledWith(409).should.equal(true)
- })
- })
- describe('when an error occurs', function () {
- beforeEach(function () {
- this.ProjectManager.getProjectDocsAndFlushIfOld = sinon
- .stub()
- .callsArgWith(3, new Error('oops'))
- this.HttpController.getProjectDocsAndFlushIfOld(
- this.req,
- this.res,
- this.next
- )
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('updateProject', function () {
- beforeEach(function () {
- this.projectHistoryId = 'history-id-123'
- this.userId = 'user-id-123'
- this.updates = [
- {
- type: 'rename-doc',
- id: 1,
- pathname: 'thesis.tex',
- newPathname: 'book.tex',
- },
- { type: 'add-doc', id: 2, pathname: 'article.tex', docLines: 'hello' },
- {
- type: 'rename-file',
- id: 3,
- pathname: 'apple.png',
- newPathname: 'banana.png',
- },
- { type: 'add-file', id: 4, url: 'filestore.example.com/4' },
- ]
- this.version = 1234567
- this.req = {
- query: {},
- body: {
- projectHistoryId: this.projectHistoryId,
- userId: this.userId,
- updates: this.updates,
- version: this.version,
- source: this.source,
- },
- params: {
- project_id: this.project_id,
- },
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.ProjectManager.updateProjectWithLocks = sinon.stub().yields()
- this.HttpController.updateProject(this.req, this.res, this.next)
- })
- it('should accept the change', function () {
- this.ProjectManager.updateProjectWithLocks
- .calledWith(
- this.project_id,
- this.projectHistoryId,
- this.userId,
- this.updates,
- this.version,
- this.source
- )
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.calledWith(204).should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.ProjectManager.updateProjectWithLocks = sinon
- .stub()
- .yields(new Error('oops'))
- this.HttpController.updateProject(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('resyncProjectHistory', function () {
- beforeEach(function () {
- this.projectHistoryId = 'history-id-123'
- this.docs = sinon.stub()
- this.files = sinon.stub()
- this.fileUpdates = sinon.stub()
- this.req = {
- query: {},
- body: {
- projectHistoryId: this.projectHistoryId,
- docs: this.docs,
- files: this.files,
- },
- params: {
- project_id: this.project_id,
- },
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(5)
- this.HttpController.resyncProjectHistory(this.req, this.res, this.next)
- })
- it('should accept the change', function () {
- this.HistoryManager.resyncProjectHistory
- .calledWith(
- this.project_id,
- this.projectHistoryId,
- this.docs,
- this.files,
- {}
- )
- .should.equal(true)
- })
- it('should return a successful No Content response', function () {
- this.res.sendStatus.should.have.been.calledWith(204)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.HistoryManager.resyncProjectHistory = sinon
- .stub()
- .callsArgWith(5, new Error('oops'))
- this.HttpController.resyncProjectHistory(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- })
- describe('appendToDoc', function () {
- beforeEach(function () {
- this.lines = ['one', 'two', 'three']
- this.source = 'dropbox'
- this.user_id = 'user-id-123'
- this.req = {
- headers: {},
- params: {
- project_id: this.project_id,
- doc_id: this.doc_id,
- },
- query: {},
- body: {
- lines: this.lines,
- source: this.source,
- user_id: this.user_id,
- undoing: (this.undoing = true),
- },
- }
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.DocumentManager.appendToDocWithLock = sinon
- .stub()
- .callsArgWith(5, null, { rev: '123' })
- this.HttpController.appendToDoc(this.req, this.res, this.next)
- })
- it('should append to the doc', function () {
- this.DocumentManager.appendToDocWithLock
- .calledWith(
- this.project_id,
- this.doc_id,
- this.lines,
- this.source,
- this.user_id
- )
- .should.equal(true)
- })
- it('should return a json response with the document rev from web', function () {
- this.res.json.calledWithMatch({ rev: '123' }).should.equal(true)
- })
- it('should log the request', function () {
- this.logger.debug
- .calledWith(
- {
- docId: this.doc_id,
- projectId: this.project_id,
- lines: this.lines,
- source: this.source,
- userId: this.user_id,
- },
- 'appending to doc via http'
- )
- .should.equal(true)
- })
- it('should time the request', function () {
- this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('when an errors occurs', function () {
- beforeEach(function () {
- this.DocumentManager.appendToDocWithLock = sinon
- .stub()
- .callsArgWith(5, new Error('oops'))
- this.HttpController.appendToDoc(this.req, this.res, this.next)
- })
- it('should call next with the error', function () {
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
- })
- describe('when the payload is too large', function () {
- beforeEach(function () {
- this.DocumentManager.appendToDocWithLock = sinon
- .stub()
- .callsArgWith(5, new Errors.FileTooLargeError())
- this.HttpController.appendToDoc(this.req, this.res, this.next)
- })
- it('should send back a 422 response', function () {
- this.res.sendStatus.calledWith(422).should.equal(true)
- })
- })
- })
- })
|