mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
989c48978a
[web] project-history expects history-v1 id in blob requests GitOrigin-RevId: d9170a12fc6070811c188b346dbac32accabbfd7
577 lines
17 KiB
JavaScript
577 lines
17 KiB
JavaScript
import sinon from 'sinon'
|
|
import { strict as esmock } from 'esmock'
|
|
import mongodb from 'mongodb-legacy'
|
|
const { ObjectId } = mongodb
|
|
|
|
const MODULE_PATH = '../../../../app/js/HttpController.js'
|
|
|
|
describe('HttpController', function () {
|
|
beforeEach(async function () {
|
|
this.UpdatesProcessor = {
|
|
processUpdatesForProject: sinon.stub().yields(),
|
|
}
|
|
this.SummarizedUpdatesManager = {
|
|
getSummarizedProjectUpdates: sinon.stub(),
|
|
}
|
|
this.DiffManager = {
|
|
getDiff: sinon.stub(),
|
|
}
|
|
this.HistoryStoreManager = {
|
|
deleteProject: sinon.stub().yields(),
|
|
getMostRecentVersion: sinon.stub(),
|
|
getProjectBlobStream: sinon.stub(),
|
|
initializeProject: sinon.stub(),
|
|
}
|
|
this.SnapshotManager = {
|
|
getFileSnapshotStream: sinon.stub(),
|
|
getProjectSnapshot: sinon.stub(),
|
|
}
|
|
this.HealthChecker = {}
|
|
this.SyncManager = {
|
|
clearResyncState: sinon.stub().yields(),
|
|
startResync: sinon.stub().yields(),
|
|
}
|
|
this.WebApiManager = {
|
|
getHistoryId: sinon.stub(),
|
|
}
|
|
this.RedisManager = {
|
|
destroyDocUpdatesQueue: sinon.stub().yields(),
|
|
clearFirstOpTimestamp: sinon.stub().yields(),
|
|
clearCachedHistoryId: sinon.stub().yields(),
|
|
}
|
|
this.ErrorRecorder = {
|
|
record: sinon.stub().yields(),
|
|
}
|
|
this.LabelsManager = {
|
|
createLabel: sinon.stub(),
|
|
deleteLabel: sinon.stub().yields(),
|
|
deleteLabelForUser: sinon.stub().yields(),
|
|
getLabels: sinon.stub(),
|
|
}
|
|
this.HistoryApiManager = {
|
|
shouldUseProjectHistory: sinon.stub(),
|
|
}
|
|
this.RetryManager = {}
|
|
this.FlushManager = {}
|
|
this.request = {}
|
|
this.pipeline = sinon.stub()
|
|
this.HttpController = await esmock(MODULE_PATH, {
|
|
request: this.request,
|
|
stream: { pipeline: this.pipeline },
|
|
'../../../../app/js/UpdatesProcessor.js': this.UpdatesProcessor,
|
|
'../../../../app/js/SummarizedUpdatesManager.js':
|
|
this.SummarizedUpdatesManager,
|
|
'../../../../app/js/DiffManager.js': this.DiffManager,
|
|
'../../../../app/js/HistoryStoreManager.js': this.HistoryStoreManager,
|
|
'../../../../app/js/SnapshotManager.js': this.SnapshotManager,
|
|
'../../../../app/js/HealthChecker.js': this.HealthChecker,
|
|
'../../../../app/js/SyncManager.js': this.SyncManager,
|
|
'../../../../app/js/WebApiManager.js': this.WebApiManager,
|
|
'../../../../app/js/RedisManager.js': this.RedisManager,
|
|
'../../../../app/js/ErrorRecorder.js': this.ErrorRecorder,
|
|
'../../../../app/js/LabelsManager.js': this.LabelsManager,
|
|
'../../../../app/js/HistoryApiManager.js': this.HistoryApiManager,
|
|
'../../../../app/js/RetryManager.js': this.RetryManager,
|
|
'../../../../app/js/FlushManager.js': this.FlushManager,
|
|
})
|
|
this.pathname = 'doc-id-123'
|
|
this.projectId = new ObjectId().toString()
|
|
this.projectOwnerId = new ObjectId().toString()
|
|
this.next = sinon.stub()
|
|
this.userId = new ObjectId().toString()
|
|
this.now = Date.now()
|
|
this.res = {
|
|
json: sinon.stub(),
|
|
send: sinon.stub(),
|
|
sendStatus: sinon.stub(),
|
|
setHeader: sinon.stub(),
|
|
}
|
|
})
|
|
|
|
describe('getProjectBlob', function () {
|
|
beforeEach(function () {
|
|
this.blobHash = 'abcd'
|
|
this.stream = {}
|
|
this.historyId = 1337
|
|
this.HistoryStoreManager.getProjectBlobStream.yields(null, this.stream)
|
|
this.HttpController.getProjectBlob(
|
|
{ params: { history_id: this.historyId, hash: this.blobHash } },
|
|
this.res,
|
|
this.next
|
|
)
|
|
})
|
|
|
|
it('should get a blob stream', function () {
|
|
this.HistoryStoreManager.getProjectBlobStream
|
|
.calledWith(this.historyId, this.blobHash)
|
|
.should.equal(true)
|
|
this.pipeline.should.have.been.calledWith(this.stream, this.res)
|
|
})
|
|
|
|
it('should set caching header', function () {
|
|
this.res.setHeader.should.have.been.calledWith(
|
|
'Cache-Control',
|
|
'private, max-age=86400'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('initializeProject', function () {
|
|
beforeEach(function () {
|
|
this.historyId = new ObjectId().toString()
|
|
this.req = { body: { historyId: this.historyId } }
|
|
this.HistoryStoreManager.initializeProject.yields(null, this.historyId)
|
|
this.HttpController.initializeProject(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should initialize the project', function () {
|
|
this.HistoryStoreManager.initializeProject.calledWith().should.equal(true)
|
|
})
|
|
|
|
it('should return the new overleaf id', function () {
|
|
this.res.json
|
|
.calledWith({ project: { id: this.historyId } })
|
|
.should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('flushProject', function () {
|
|
beforeEach(function () {
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
},
|
|
query: {},
|
|
}
|
|
this.HttpController.flushProject(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should process the updates', function () {
|
|
this.UpdatesProcessor.processUpdatesForProject
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return a success code', function () {
|
|
this.res.sendStatus.calledWith(204).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('getDiff', function () {
|
|
beforeEach(function () {
|
|
this.from = 42
|
|
this.to = 45
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
},
|
|
query: {
|
|
pathname: this.pathname,
|
|
from: this.from,
|
|
to: this.to,
|
|
},
|
|
}
|
|
this.diff = [{ u: 'mock-diff' }]
|
|
this.DiffManager.getDiff.yields(null, this.diff)
|
|
this.HttpController.getDiff(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should get the diff', function () {
|
|
this.DiffManager.getDiff.should.have.been.calledWith(
|
|
this.projectId,
|
|
this.pathname,
|
|
this.from,
|
|
this.to
|
|
)
|
|
})
|
|
|
|
it('should return the diff', function () {
|
|
this.res.json.calledWith({ diff: this.diff }).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('getUpdates', function () {
|
|
beforeEach(function () {
|
|
this.before = Date.now()
|
|
this.nextBeforeTimestamp = this.before - 100
|
|
this.min_count = 10
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
},
|
|
query: {
|
|
before: this.before,
|
|
min_count: this.min_count,
|
|
},
|
|
}
|
|
this.updates = [{ i: 'mock-summarized-updates', p: 10 }]
|
|
this.SummarizedUpdatesManager.getSummarizedProjectUpdates.yields(
|
|
null,
|
|
this.updates,
|
|
this.nextBeforeTimestamp
|
|
)
|
|
this.HttpController.getUpdates(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should get the updates', function () {
|
|
this.SummarizedUpdatesManager.getSummarizedProjectUpdates.should.have.been.calledWith(
|
|
this.projectId,
|
|
{
|
|
before: this.before,
|
|
min_count: this.min_count,
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should return the formatted updates', function () {
|
|
this.res.json.should.have.been.calledWith({
|
|
updates: this.updates,
|
|
nextBeforeTimestamp: this.nextBeforeTimestamp,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('latestVersion', function () {
|
|
beforeEach(function () {
|
|
this.historyId = 1234
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
},
|
|
}
|
|
|
|
this.version = 99
|
|
this.lastChange = {
|
|
v2Authors: ['1234'],
|
|
timestamp: '2016-08-16T10:44:40.227Z',
|
|
}
|
|
this.versionInfo = {
|
|
version: this.version,
|
|
v2Authors: ['1234'],
|
|
timestamp: '2016-08-16T10:44:40.227Z',
|
|
}
|
|
this.WebApiManager.getHistoryId.yields(null, this.historyId)
|
|
this.HistoryStoreManager.getMostRecentVersion.yields(
|
|
null,
|
|
this.version,
|
|
{},
|
|
this.lastChange
|
|
)
|
|
this.HttpController.latestVersion(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should process the updates', function () {
|
|
this.UpdatesProcessor.processUpdatesForProject
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the ol project id', function () {
|
|
this.WebApiManager.getHistoryId
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the latest version', function () {
|
|
this.HistoryStoreManager.getMostRecentVersion
|
|
.calledWith(this.projectId, this.historyId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return version number', function () {
|
|
this.res.json.calledWith(this.versionInfo).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('resyncProject', function () {
|
|
beforeEach(function () {
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
},
|
|
query: {},
|
|
body: {},
|
|
}
|
|
this.HttpController.resyncProject(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should resync the project', function () {
|
|
this.SyncManager.startResync.calledWith(this.projectId).should.equal(true)
|
|
})
|
|
|
|
it('should flush the queue', function () {
|
|
this.UpdatesProcessor.processUpdatesForProject
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return 204', function () {
|
|
this.res.sendStatus.calledWith(204).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('getFileSnapshot', function () {
|
|
beforeEach(function () {
|
|
this.version = 42
|
|
this.pathname = 'foo.tex'
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
version: this.version,
|
|
pathname: this.pathname,
|
|
},
|
|
}
|
|
this.res = { mock: 'res' }
|
|
this.stream = {}
|
|
this.SnapshotManager.getFileSnapshotStream.yields(null, this.stream)
|
|
this.HttpController.getFileSnapshot(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should get the snapshot', function () {
|
|
this.SnapshotManager.getFileSnapshotStream.should.have.been.calledWith(
|
|
this.projectId,
|
|
this.version,
|
|
this.pathname
|
|
)
|
|
})
|
|
|
|
it('should pipe the returned stream into the response', function () {
|
|
this.pipeline.should.have.been.calledWith(this.stream, this.res)
|
|
})
|
|
})
|
|
|
|
describe('getProjectSnapshot', function () {
|
|
beforeEach(function () {
|
|
this.version = 42
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
version: this.version,
|
|
},
|
|
}
|
|
this.res = { json: sinon.stub() }
|
|
this.snapshotData = { one: 1 }
|
|
this.SnapshotManager.getProjectSnapshot.yields(null, this.snapshotData)
|
|
this.HttpController.getProjectSnapshot(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should get the snapshot', function () {
|
|
this.SnapshotManager.getProjectSnapshot.should.have.been.calledWith(
|
|
this.projectId,
|
|
this.version
|
|
)
|
|
})
|
|
|
|
it('should send json response', function () {
|
|
this.res.json.calledWith(this.snapshotData).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('getLabels', function () {
|
|
beforeEach(function () {
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
},
|
|
}
|
|
this.labels = ['label-1', 'label-2']
|
|
this.LabelsManager.getLabels.yields(null, this.labels)
|
|
})
|
|
|
|
describe('project history is enabled', function () {
|
|
beforeEach(function () {
|
|
this.HistoryApiManager.shouldUseProjectHistory.yields(null, true)
|
|
this.HttpController.getLabels(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should get the labels for a project', function () {
|
|
this.LabelsManager.getLabels
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return the labels', function () {
|
|
this.res.json.calledWith(this.labels).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('project history is not enabled', function () {
|
|
beforeEach(function () {
|
|
this.HistoryApiManager.shouldUseProjectHistory.yields(null, false)
|
|
this.HttpController.getLabels(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should return 409', function () {
|
|
this.res.sendStatus.calledWith(409).should.equal(true)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('createLabel', function () {
|
|
beforeEach(function () {
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
user_id: this.userId,
|
|
},
|
|
body: {
|
|
version: (this.version = 'label-1'),
|
|
comment: (this.comment = 'a comment'),
|
|
created_at: (this.created_at = Date.now().toString()),
|
|
validate_exists: true,
|
|
},
|
|
}
|
|
this.label = { _id: new ObjectId() }
|
|
this.LabelsManager.createLabel.yields(null, this.label)
|
|
})
|
|
|
|
describe('project history is enabled', function () {
|
|
beforeEach(function () {
|
|
this.HistoryApiManager.shouldUseProjectHistory.yields(null, true)
|
|
this.HttpController.createLabel(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should create a label for a project', function () {
|
|
this.LabelsManager.createLabel.should.have.been.calledWith(
|
|
this.projectId,
|
|
this.userId,
|
|
this.version,
|
|
this.comment,
|
|
this.created_at,
|
|
true
|
|
)
|
|
})
|
|
|
|
it('should return the label', function () {
|
|
this.res.json.calledWith(this.label).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('validate_exists = false is passed', function () {
|
|
beforeEach(function () {
|
|
this.req.body.validate_exists = false
|
|
this.HistoryApiManager.shouldUseProjectHistory.yields(null, true)
|
|
this.HttpController.createLabel(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should create a label for a project', function () {
|
|
this.LabelsManager.createLabel
|
|
.calledWith(
|
|
this.projectId,
|
|
this.userId,
|
|
this.version,
|
|
this.comment,
|
|
this.created_at,
|
|
false
|
|
)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return the label', function () {
|
|
this.res.json.calledWith(this.label).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('project history is not enabled', function () {
|
|
beforeEach(function () {
|
|
this.HistoryApiManager.shouldUseProjectHistory.yields(null, false)
|
|
this.HttpController.createLabel(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should return 409', function () {
|
|
this.res.sendStatus.calledWith(409).should.equal(true)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('deleteLabelForUser', function () {
|
|
beforeEach(function () {
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
user_id: this.userId,
|
|
label_id: (this.label_id = new ObjectId()),
|
|
},
|
|
}
|
|
this.HttpController.deleteLabelForUser(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should delete a label for a project', function () {
|
|
this.LabelsManager.deleteLabelForUser
|
|
.calledWith(this.projectId, this.userId, this.label_id)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return 204', function () {
|
|
this.res.sendStatus.calledWith(204).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('deleteLabel', function () {
|
|
beforeEach(function () {
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
label_id: (this.label_id = new ObjectId()),
|
|
},
|
|
}
|
|
this.HttpController.deleteLabel(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should delete a label for a project', function () {
|
|
this.LabelsManager.deleteLabel
|
|
.calledWith(this.projectId, this.label_id)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return 204', function () {
|
|
this.res.sendStatus.calledWith(204).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('deleteProject', function () {
|
|
beforeEach(function () {
|
|
this.req = {
|
|
params: {
|
|
project_id: this.projectId,
|
|
},
|
|
}
|
|
this.WebApiManager.getHistoryId
|
|
.withArgs(this.projectId)
|
|
.yields(null, this.historyId)
|
|
this.HttpController.deleteProject(this.req, this.res, this.next)
|
|
})
|
|
|
|
it('should delete the updates queue', function () {
|
|
this.RedisManager.destroyDocUpdatesQueue.should.have.been.calledWith(
|
|
this.projectId
|
|
)
|
|
})
|
|
|
|
it('should clear the first op timestamp', function () {
|
|
this.RedisManager.clearFirstOpTimestamp.should.have.been.calledWith(
|
|
this.projectId
|
|
)
|
|
})
|
|
|
|
it('should clear the cached history id', function () {
|
|
this.RedisManager.clearCachedHistoryId.should.have.been.calledWith(
|
|
this.projectId
|
|
)
|
|
})
|
|
|
|
it('should clear the resync state', function () {
|
|
this.SyncManager.clearResyncState.should.have.been.calledWith(
|
|
this.projectId
|
|
)
|
|
})
|
|
|
|
it('should clear any failure record', function () {
|
|
this.ErrorRecorder.record.should.have.been.calledWith(
|
|
this.projectId,
|
|
0,
|
|
null
|
|
)
|
|
})
|
|
})
|
|
})
|