mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-24 21:12:38 -04:00
0f0d562786
[misc] add metrics for document processing/broadcasting GitOrigin-RevId: d81de0dfb7a91863547631580f3c85f569718130
1107 lines
32 KiB
JavaScript
1107 lines
32 KiB
JavaScript
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 = {}),
|
|
'./ProjectFlusher': { flushAllProjects() {} },
|
|
'./DeleteQueueManager': (this.DeleteQueueManager = {}),
|
|
'./RedisManager': (this.RedisManager = {
|
|
DOC_OPS_TTL: 42,
|
|
}),
|
|
'./Metrics': (this.Metrics = {}),
|
|
'./Errors': Errors,
|
|
'@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.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.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
|
|
.calledWith({
|
|
id: this.doc_id,
|
|
lines: this.lines,
|
|
version: this.version,
|
|
ops: [],
|
|
ranges: this.ranges,
|
|
pathname: this.pathname,
|
|
ttlInS: 42,
|
|
})
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should log the request', function () {
|
|
this.logger.debug
|
|
.calledWith(
|
|
{ docId: this.doc_id, projectId: this.project_id },
|
|
'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.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
|
|
.calledWith({
|
|
id: this.doc_id,
|
|
lines: this.lines,
|
|
version: this.version,
|
|
ops: this.ops,
|
|
ranges: this.ranges,
|
|
pathname: this.pathname,
|
|
ttlInS: 42,
|
|
})
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should log the request', function () {
|
|
this.logger.debug
|
|
.calledWith(
|
|
{ docId: this.doc_id, projectId: this.project_id },
|
|
'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('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(6, 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
|
|
)
|
|
.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(6, 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)
|
|
})
|
|
})
|
|
})
|
|
})
|