mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-27 13:31:28 +00:00
ee85d948e2
GitOrigin-RevId: ef2ef77e26df59d1af3df6dc664e284d3c70102d
501 lines
15 KiB
JavaScript
501 lines
15 KiB
JavaScript
/* eslint-disable
|
|
no-return-assign,
|
|
no-undef,
|
|
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
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
*/
|
|
import sinon from 'sinon'
|
|
import { expect } from 'chai'
|
|
import { strict as esmock } from 'esmock'
|
|
import Core from 'overleaf-editor-core'
|
|
import BPromise from 'bluebird'
|
|
import * as Errors from '../../../../app/js/Errors.js'
|
|
|
|
const MODULE_PATH = '../../../../app/js/SnapshotManager.js'
|
|
|
|
describe('SnapshotManager', function () {
|
|
beforeEach(async function () {
|
|
this.HistoryStoreManager = {
|
|
getBlobStore: sinon.stub(),
|
|
getChunkAtVersion: sinon.stub(),
|
|
getMostRecentChunk: sinon.stub(),
|
|
getProjectBlobStream: sinon.stub(),
|
|
}
|
|
this.WebApiManager = {
|
|
getHistoryId: sinon.stub(),
|
|
}
|
|
this.SnapshotManager = await esmock(MODULE_PATH, {
|
|
'overleaf-editor-core': Core,
|
|
'../../../../app/js/HistoryStoreManager.js': this.HistoryStoreManager,
|
|
'../../../../app/js/WebApiManager.js': this.WebApiManager,
|
|
'../../../../app/js/Errors.js': Errors,
|
|
})
|
|
this.projectId = 'project-id-123'
|
|
this.historyId = 'ol-project-id-123'
|
|
return (this.callback = sinon.stub())
|
|
})
|
|
|
|
describe('getFileSnapshotStream', function () {
|
|
beforeEach(function () {
|
|
this.WebApiManager.getHistoryId.yields(null, this.historyId)
|
|
return this.HistoryStoreManager.getChunkAtVersion.yields(null, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'main.tex': {
|
|
hash: '35c9bd86574d61dcadbce2fdd3d4a0684272c6ea',
|
|
stringLength: 41,
|
|
},
|
|
'binary.png': {
|
|
hash: 'c6654ea913979e13e22022653d284444f284a172',
|
|
byteLength: 41,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'main.tex',
|
|
textOperation: [41, '\n\nSeven eight'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'main.tex',
|
|
textOperation: [54, ' nine'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
authors: [
|
|
{
|
|
id: 31,
|
|
email: 'james.allen@overleaf.com',
|
|
name: 'James',
|
|
},
|
|
],
|
|
},
|
|
})
|
|
})
|
|
|
|
describe('of a text file', function () {
|
|
beforeEach(function (done) {
|
|
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
|
getString: BPromise.promisify(
|
|
(this.getString = sinon.stub().yields(
|
|
null,
|
|
`\
|
|
Hello world
|
|
|
|
One two three
|
|
|
|
Four five six\
|
|
`.replace(/^\t/g, '')
|
|
))
|
|
),
|
|
})
|
|
this.SnapshotManager.getFileSnapshotStream(
|
|
this.projectId,
|
|
5,
|
|
'main.tex',
|
|
(error, stream) => {
|
|
this.stream = stream
|
|
return done(error)
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should get the overleaf id', function () {
|
|
return this.WebApiManager.getHistoryId
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the chunk', function () {
|
|
return this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(this.projectId, this.historyId, 5)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the blob of the starting snapshot', function () {
|
|
return this.getString
|
|
.calledWith('35c9bd86574d61dcadbce2fdd3d4a0684272c6ea')
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return a string stream with the text content', function () {
|
|
return expect(this.stream.read().toString()).to.equal(
|
|
`\
|
|
Hello world
|
|
|
|
One two three
|
|
|
|
Four five six
|
|
|
|
Seven eight nine\
|
|
`.replace(/^\t/g, '')
|
|
)
|
|
})
|
|
|
|
describe('on blob store error', function () {
|
|
beforeEach(function () {
|
|
this.error = new Error('ESOCKETTIMEDOUT')
|
|
this.HistoryStoreManager.getBlobStore
|
|
.withArgs(this.historyId)
|
|
.returns({
|
|
getString: BPromise.promisify(sinon.stub().throws(this.error)),
|
|
})
|
|
})
|
|
|
|
it('should call back with error', function (done) {
|
|
this.SnapshotManager.getFileSnapshotStream(
|
|
this.projectId,
|
|
5,
|
|
'main.tex',
|
|
error => {
|
|
expect(error).to.exist
|
|
expect(error.name).to.equal(this.error.name)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('of a binary file', function () {
|
|
beforeEach(function (done) {
|
|
this.HistoryStoreManager.getProjectBlobStream
|
|
.withArgs(this.historyId)
|
|
.yields(null, (this.stream = 'mock-stream'))
|
|
return this.SnapshotManager.getFileSnapshotStream(
|
|
this.projectId,
|
|
5,
|
|
'binary.png',
|
|
(error, returnedStream) => {
|
|
this.returnedStream = returnedStream
|
|
return done(error)
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should get the overleaf id', function () {
|
|
return this.WebApiManager.getHistoryId
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the chunk', function () {
|
|
return this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(this.projectId, this.historyId, 5)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the blob of the starting snapshot', function () {
|
|
return this.HistoryStoreManager.getProjectBlobStream
|
|
.calledWith(
|
|
this.historyId,
|
|
'c6654ea913979e13e22022653d284444f284a172'
|
|
)
|
|
.should.equal(true)
|
|
})
|
|
|
|
return it('should return a stream with the blob content', function () {
|
|
return expect(this.returnedStream).to.equal(this.stream)
|
|
})
|
|
})
|
|
|
|
return describe("when the file doesn't exist", function () {
|
|
beforeEach(function (done) {
|
|
return this.SnapshotManager.getFileSnapshotStream(
|
|
this.projectId,
|
|
5,
|
|
'not-here.png',
|
|
(error, returnedStream) => {
|
|
this.error = error
|
|
this.returnedStream = returnedStream
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
return it('should return a NotFoundError', function () {
|
|
expect(this.error).to.exist
|
|
expect(this.error.message).to.equal('not-here.png not found')
|
|
return expect(this.error).to.be.an.instanceof(Errors.NotFoundError)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('getProjectSnapshot', function () {
|
|
beforeEach(function () {
|
|
this.WebApiManager.getHistoryId.yields(null, this.historyId)
|
|
return this.HistoryStoreManager.getChunkAtVersion.yields(null, {
|
|
chunk: (this.chunk = {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'main.tex': {
|
|
hash: '35c9bd86574d61dcadbce2fdd3d4a0684272c6ea',
|
|
stringLength: 41,
|
|
},
|
|
'unchanged.tex': {
|
|
hash: '35c9bd86574d61dcadbce2fdd3d4a0684272c6ea',
|
|
stringLength: 41,
|
|
},
|
|
'binary.png': {
|
|
hash: 'c6654ea913979e13e22022653d284444f284a172',
|
|
byteLength: 41,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'main.tex',
|
|
textOperation: [41, '\n\nSeven eight'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'main.tex',
|
|
textOperation: [54, ' nine'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
authors: [
|
|
{
|
|
id: 31,
|
|
email: 'james.allen@overleaf.com',
|
|
name: 'James',
|
|
},
|
|
],
|
|
}),
|
|
})
|
|
})
|
|
|
|
describe('of project', function () {
|
|
beforeEach(function (done) {
|
|
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
|
getString: BPromise.promisify(
|
|
(this.getString = sinon.stub().yields(
|
|
null,
|
|
`\
|
|
Hello world
|
|
|
|
One two three
|
|
|
|
Four five six\
|
|
`.replace(/^\t/g, '')
|
|
))
|
|
),
|
|
})
|
|
this.SnapshotManager.getProjectSnapshot(
|
|
this.projectId,
|
|
5,
|
|
(error, data) => {
|
|
this.data = data
|
|
done(error)
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should get the overleaf id', function () {
|
|
return this.WebApiManager.getHistoryId
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the chunk', function () {
|
|
return this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(this.projectId, this.historyId, 5)
|
|
.should.equal(true)
|
|
})
|
|
|
|
return it('should produce the snapshot file data', function () {
|
|
expect(this.data).to.have.all.keys(['files', 'projectId'])
|
|
expect(this.data.projectId).to.equal('project-id-123')
|
|
expect(this.data.files['main.tex']).to.exist
|
|
expect(this.data.files['unchanged.tex']).to.exist
|
|
expect(this.data.files['binary.png']).to.exist
|
|
// files with operations in the chunk should return content only
|
|
expect(this.data.files['main.tex'].data.content).to.equal(
|
|
'Hello world\n\nOne two three\n\nFour five six\n\nSeven eight nine'
|
|
)
|
|
expect(this.data.files['main.tex'].data.hash).to.not.exist
|
|
// unchanged files in the chunk should return hash only
|
|
expect(this.data.files['unchanged.tex'].data.hash).to.equal(
|
|
'35c9bd86574d61dcadbce2fdd3d4a0684272c6ea'
|
|
)
|
|
expect(this.data.files['unchanged.tex'].data.content).to.not.exist
|
|
return expect(this.data.files['binary.png'].data.hash).to.equal(
|
|
'c6654ea913979e13e22022653d284444f284a172'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('on blob store error', function () {
|
|
beforeEach(function () {
|
|
this.error = new Error('ESOCKETTIMEDOUT')
|
|
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
|
getString: BPromise.promisify(sinon.stub().yields(this.error)),
|
|
})
|
|
})
|
|
|
|
it('should call back with error', function (done) {
|
|
this.SnapshotManager.getProjectSnapshot(this.projectId, 5, error => {
|
|
expect(error).to.exist
|
|
expect(error.message).to.equal(this.error.message)
|
|
|
|
done()
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
return describe('getLatestSnapshot', function () {
|
|
describe('for a project', function () {
|
|
beforeEach(function (done) {
|
|
this.HistoryStoreManager.getMostRecentChunk.yields(null, {
|
|
chunk: (this.chunk = {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'main.tex': {
|
|
hash: '35c9bd86574d61dcadbce2fdd3d4a0684272c6ea',
|
|
stringLength: 41,
|
|
},
|
|
'binary.png': {
|
|
hash: 'c6654ea913979e13e22022653d284444f284a172',
|
|
byteLength: 41,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'main.tex',
|
|
textOperation: [41, '\n\nSeven eight'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'main.tex',
|
|
textOperation: [54, ' nine'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
authors: [
|
|
{
|
|
id: 31,
|
|
email: 'james.allen@overleaf.com',
|
|
name: 'James',
|
|
},
|
|
],
|
|
}),
|
|
})
|
|
|
|
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
|
getString: BPromise.promisify(
|
|
(this.getString = sinon.stub().yields(
|
|
null,
|
|
`\
|
|
Hello world
|
|
|
|
One two three
|
|
|
|
Four five six\
|
|
`.replace(/^\t/g, '')
|
|
))
|
|
),
|
|
})
|
|
this.SnapshotManager.getLatestSnapshot(
|
|
this.projectId,
|
|
this.historyId,
|
|
(error, data) => {
|
|
this.data = data
|
|
done(error)
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should get the chunk', function () {
|
|
return this.HistoryStoreManager.getMostRecentChunk
|
|
.calledWith(this.projectId, this.historyId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
return it('should produce the snapshot file data', function () {
|
|
expect(this.data).to.have.all.keys(['main.tex', 'binary.png'])
|
|
expect(this.data['main.tex']).to.exist
|
|
expect(this.data['binary.png']).to.exist
|
|
expect(this.data['main.tex'].getStringLength()).to.equal(59)
|
|
expect(this.data['binary.png'].getByteLength()).to.equal(41)
|
|
return expect(this.data['binary.png'].getHash()).to.equal(
|
|
'c6654ea913979e13e22022653d284444f284a172'
|
|
)
|
|
})
|
|
})
|
|
|
|
return describe('when the chunk is empty', function () {
|
|
beforeEach(function (done) {
|
|
this.HistoryStoreManager.getMostRecentChunk.yields(null)
|
|
return this.SnapshotManager.getLatestSnapshot(
|
|
this.projectId,
|
|
this.historyId,
|
|
(error, data) => {
|
|
this.error = error
|
|
this.data = data
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should get the chunk', function () {
|
|
return this.HistoryStoreManager.getMostRecentChunk
|
|
.calledWith(this.projectId, this.historyId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
return it('return an error', function () {
|
|
expect(this.error).to.exist
|
|
return expect(this.error.message).to.equal('undefined chunk')
|
|
})
|
|
})
|
|
})
|
|
})
|