mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #17745 from overleaf/em-promisify-snapshot-manager
Promisify SnapshotManager GitOrigin-RevId: 1fa7124da3aa3e0be5db372e68e286d63f496a97
This commit is contained in:
parent
5f8db6ee23
commit
ab17eb150d
5 changed files with 202 additions and 285 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -43042,7 +43042,6 @@
|
||||||
"@overleaf/settings": "*",
|
"@overleaf/settings": "*",
|
||||||
"async": "^3.2.2",
|
"async": "^3.2.2",
|
||||||
"aws-sdk": "^2.650.0",
|
"aws-sdk": "^2.650.0",
|
||||||
"bluebird": "^3.7.2",
|
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"bunyan": "^1.8.15",
|
"bunyan": "^1.8.15",
|
||||||
"byline": "^4.2.1",
|
"byline": "^4.2.1",
|
||||||
|
@ -51641,7 +51640,6 @@
|
||||||
"@overleaf/settings": "*",
|
"@overleaf/settings": "*",
|
||||||
"async": "^3.2.2",
|
"async": "^3.2.2",
|
||||||
"aws-sdk": "^2.650.0",
|
"aws-sdk": "^2.650.0",
|
||||||
"bluebird": "^3.7.2",
|
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"bunyan": "^1.8.15",
|
"bunyan": "^1.8.15",
|
||||||
"byline": "^4.2.1",
|
"byline": "^4.2.1",
|
||||||
|
|
|
@ -36,10 +36,16 @@ _mocks.getMostRecentChunk = (projectId, historyId, callback) => {
|
||||||
_requestChunk({ path, json: true }, callback)
|
_requestChunk({ path, json: true }, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMostRecentChunk(...args) {
|
/**
|
||||||
_mocks.getMostRecentChunk(...args)
|
* @param {Callback} callback
|
||||||
|
*/
|
||||||
|
export function getMostRecentChunk(projectId, historyId, callback) {
|
||||||
|
_mocks.getMostRecentChunk(projectId, historyId, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Callback} callback
|
||||||
|
*/
|
||||||
export function getChunkAtVersion(projectId, historyId, version, callback) {
|
export function getChunkAtVersion(projectId, historyId, version, callback) {
|
||||||
const path = `projects/${historyId}/versions/${version}/history`
|
const path = `projects/${historyId}/versions/${version}/history`
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -172,6 +178,9 @@ export function getProjectBlob(historyId, blobHash, callback) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Callback} callback
|
||||||
|
*/
|
||||||
export function getProjectBlobStream(historyId, blobHash, callback) {
|
export function getProjectBlobStream(historyId, blobHash, callback) {
|
||||||
const url = `${Settings.overleaf.history.host}/projects/${historyId}/blobs/${blobHash}`
|
const url = `${Settings.overleaf.history.host}/projects/${historyId}/blobs/${blobHash}`
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import { promisify } from 'util'
|
import { callbackify } from 'util'
|
||||||
import Core from 'overleaf-editor-core'
|
import Core from 'overleaf-editor-core'
|
||||||
import { Readable as StringStream } from 'stream'
|
import { Readable as StringStream } from 'stream'
|
||||||
import BPromise from 'bluebird'
|
|
||||||
import OError from '@overleaf/o-error'
|
import OError from '@overleaf/o-error'
|
||||||
import * as HistoryStoreManager from './HistoryStoreManager.js'
|
import * as HistoryStoreManager from './HistoryStoreManager.js'
|
||||||
import * as WebApiManager from './WebApiManager.js'
|
import * as WebApiManager from './WebApiManager.js'
|
||||||
import * as Errors from './Errors.js'
|
import * as Errors from './Errors.js'
|
||||||
|
|
||||||
/** @typedef {import('overleaf-editor-core').Snapshot} Snapshot */
|
/**
|
||||||
|
* @typedef {import('stream').Readable} ReadableStream
|
||||||
|
* @typedef {import('overleaf-editor-core').Snapshot} Snapshot
|
||||||
|
*/
|
||||||
|
|
||||||
StringStream.prototype._read = function () {}
|
StringStream.prototype._read = function () {}
|
||||||
|
|
||||||
|
@ -20,162 +22,128 @@ const MAX_REQUESTS = 4 // maximum number of parallel requests to v1 history serv
|
||||||
* @param {string} projectId
|
* @param {string} projectId
|
||||||
* @param {number} version
|
* @param {number} version
|
||||||
* @param {string} pathname
|
* @param {string} pathname
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
*/
|
||||||
export function getFileSnapshotStream(projectId, version, pathname, callback) {
|
async function getFileSnapshotStream(projectId, version, pathname) {
|
||||||
_getSnapshotAtVersion(projectId, version, (error, snapshot) => {
|
const snapshot = await _getSnapshotAtVersion(projectId, version)
|
||||||
if (error) {
|
|
||||||
return callback(OError.tag(error))
|
|
||||||
}
|
|
||||||
const file = snapshot.getFile(pathname)
|
|
||||||
if (file == null) {
|
|
||||||
error = new Errors.NotFoundError(`${pathname} not found`, {
|
|
||||||
projectId,
|
|
||||||
version,
|
|
||||||
pathname,
|
|
||||||
})
|
|
||||||
return callback(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
WebApiManager.getHistoryId(projectId, (err, historyId) => {
|
const file = snapshot.getFile(pathname)
|
||||||
if (err) {
|
if (file == null) {
|
||||||
return callback(OError.tag(err))
|
throw new Errors.NotFoundError(`${pathname} not found`, {
|
||||||
}
|
projectId,
|
||||||
if (file.isEditable()) {
|
version,
|
||||||
file
|
pathname,
|
||||||
.load('eager', HistoryStoreManager.getBlobStore(historyId))
|
|
||||||
.then(() => {
|
|
||||||
const stream = new StringStream()
|
|
||||||
stream.push(file.getContent({ filterTrackedDeletes: true }))
|
|
||||||
stream.push(null)
|
|
||||||
callback(null, stream)
|
|
||||||
})
|
|
||||||
.catch(err => callback(err))
|
|
||||||
} else {
|
|
||||||
HistoryStoreManager.getProjectBlobStream(
|
|
||||||
historyId,
|
|
||||||
file.getHash(),
|
|
||||||
callback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const historyId = await WebApiManager.promises.getHistoryId(projectId)
|
||||||
|
if (file.isEditable()) {
|
||||||
|
await file.load('eager', HistoryStoreManager.getBlobStore(historyId))
|
||||||
|
const stream = new StringStream()
|
||||||
|
stream.push(file.getContent({ filterTrackedDeletes: true }))
|
||||||
|
stream.push(null)
|
||||||
|
return stream
|
||||||
|
} else {
|
||||||
|
return await HistoryStoreManager.promises.getProjectBlobStream(
|
||||||
|
historyId,
|
||||||
|
file.getHash()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns project snapshot containing the document content for files with
|
// Returns project snapshot containing the document content for files with
|
||||||
// text operations in the relevant chunk, and hashes for unmodified/binary
|
// text operations in the relevant chunk, and hashes for unmodified/binary
|
||||||
// files. Used by git bridge to get the state of the project.
|
// files. Used by git bridge to get the state of the project.
|
||||||
export function getProjectSnapshot(projectId, version, callback) {
|
async function getProjectSnapshot(projectId, version) {
|
||||||
_getSnapshotAtVersion(projectId, version, (error, snapshot) => {
|
const snapshot = await _getSnapshotAtVersion(projectId, version)
|
||||||
if (error) {
|
const historyId = await WebApiManager.promises.getHistoryId(projectId)
|
||||||
return callback(OError.tag(error))
|
await _loadFilesLimit(
|
||||||
}
|
snapshot,
|
||||||
WebApiManager.getHistoryId(projectId, (err, historyId) => {
|
'eager',
|
||||||
if (err) {
|
HistoryStoreManager.getBlobStore(historyId)
|
||||||
return callback(OError.tag(err))
|
)
|
||||||
|
return {
|
||||||
|
projectId,
|
||||||
|
files: snapshot.getFileMap().map(file => {
|
||||||
|
if (!file) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
_loadFilesLimit(
|
const content = file.getContent({
|
||||||
snapshot,
|
filterTrackedDeletes: true,
|
||||||
'eager',
|
})
|
||||||
HistoryStoreManager.getBlobStore(historyId)
|
if (content === null) {
|
||||||
)
|
return { data: { hash: file.getHash() } }
|
||||||
.then(() => {
|
}
|
||||||
const data = {
|
return { data: { content } }
|
||||||
projectId,
|
}),
|
||||||
files: snapshot.getFileMap().map(file => {
|
}
|
||||||
if (!file) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const content = file.getContent({
|
|
||||||
filterTrackedDeletes: true,
|
|
||||||
})
|
|
||||||
if (content === null) {
|
|
||||||
return { data: { hash: file.getHash() } }
|
|
||||||
}
|
|
||||||
return { data: { content } }
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
callback(null, data)
|
|
||||||
})
|
|
||||||
.catch(callback)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} projectId
|
* @param {string} projectId
|
||||||
* @param {number} version
|
* @param {number} version
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
*/
|
||||||
function _getSnapshotAtVersion(projectId, version, callback) {
|
async function _getSnapshotAtVersion(projectId, version) {
|
||||||
WebApiManager.getHistoryId(projectId, (error, historyId) => {
|
const historyId = await WebApiManager.promises.getHistoryId(projectId)
|
||||||
if (error) {
|
const data = await HistoryStoreManager.promises.getChunkAtVersion(
|
||||||
return callback(OError.tag(error))
|
projectId,
|
||||||
}
|
historyId,
|
||||||
HistoryStoreManager.getChunkAtVersion(
|
version
|
||||||
projectId,
|
|
||||||
historyId,
|
|
||||||
version,
|
|
||||||
(error, data) => {
|
|
||||||
if (error) {
|
|
||||||
return callback(OError.tag(error))
|
|
||||||
}
|
|
||||||
const chunk = Core.Chunk.fromRaw(data.chunk)
|
|
||||||
const snapshot = chunk.getSnapshot()
|
|
||||||
const changes = chunk
|
|
||||||
.getChanges()
|
|
||||||
.slice(0, version - chunk.getStartVersion())
|
|
||||||
snapshot.applyAll(changes)
|
|
||||||
callback(null, snapshot)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLatestSnapshot(projectId, historyId, callback) {
|
|
||||||
HistoryStoreManager.getMostRecentChunk(projectId, historyId, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err)
|
|
||||||
}
|
|
||||||
if (data == null || data.chunk == null) {
|
|
||||||
return callback(new OError('undefined chunk'))
|
|
||||||
}
|
|
||||||
// apply all the changes in the chunk to get the current snapshot
|
|
||||||
const chunk = Core.Chunk.fromRaw(data.chunk)
|
|
||||||
const snapshot = chunk.getSnapshot()
|
|
||||||
const changes = chunk.getChanges()
|
|
||||||
snapshot.applyAll(changes)
|
|
||||||
snapshot
|
|
||||||
.loadFiles('lazy', HistoryStoreManager.getBlobStore(historyId))
|
|
||||||
.then(snapshotFiles => callback(null, snapshotFiles))
|
|
||||||
.catch(err => callback(err))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function _loadFilesLimit(snapshot, kind, blobStore) {
|
|
||||||
// bluebird promises only support a limit on concurrency for map()
|
|
||||||
// so make an array of the files we need to load
|
|
||||||
const fileList = []
|
|
||||||
snapshot.fileMap.map(file => fileList.push(file))
|
|
||||||
// load the files in parallel with a limit on the concurrent requests
|
|
||||||
return BPromise.map(
|
|
||||||
fileList,
|
|
||||||
file => {
|
|
||||||
// only load changed files or files with tracked changes, others can be
|
|
||||||
// dereferenced from their blobs (this method is only used by the git
|
|
||||||
// bridge which understands how to load blobs).
|
|
||||||
if (!file.isEditable() || (file.getHash() && !file.getRangesHash())) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return file.load(kind, blobStore)
|
|
||||||
},
|
|
||||||
{ concurrency: MAX_REQUESTS }
|
|
||||||
)
|
)
|
||||||
|
const chunk = Core.Chunk.fromRaw(data.chunk)
|
||||||
|
const snapshot = chunk.getSnapshot()
|
||||||
|
const changes = chunk.getChanges().slice(0, version - chunk.getStartVersion())
|
||||||
|
snapshot.applyAll(changes)
|
||||||
|
return snapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLatestSnapshot(projectId, historyId) {
|
||||||
|
const data = await HistoryStoreManager.promises.getMostRecentChunk(
|
||||||
|
projectId,
|
||||||
|
historyId
|
||||||
|
)
|
||||||
|
if (data == null || data.chunk == null) {
|
||||||
|
throw new OError('undefined chunk')
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply all the changes in the chunk to get the current snapshot
|
||||||
|
const chunk = Core.Chunk.fromRaw(data.chunk)
|
||||||
|
const snapshot = chunk.getSnapshot()
|
||||||
|
const changes = chunk.getChanges()
|
||||||
|
snapshot.applyAll(changes)
|
||||||
|
const snapshotFiles = await snapshot.loadFiles(
|
||||||
|
'lazy',
|
||||||
|
HistoryStoreManager.getBlobStore(historyId)
|
||||||
|
)
|
||||||
|
return snapshotFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _loadFilesLimit(snapshot, kind, blobStore) {
|
||||||
|
await snapshot.fileMap.mapAsync(async file => {
|
||||||
|
// only load changed files or files with tracked changes, others can be
|
||||||
|
// dereferenced from their blobs (this method is only used by the git
|
||||||
|
// bridge which understands how to load blobs).
|
||||||
|
if (!file.isEditable() || (file.getHash() && !file.getRangesHash())) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await file.load(kind, blobStore)
|
||||||
|
}, MAX_REQUESTS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPORTS
|
||||||
|
|
||||||
|
const getFileSnapshotStreamCb = callbackify(getFileSnapshotStream)
|
||||||
|
const getProjectSnapshotCb = callbackify(getProjectSnapshot)
|
||||||
|
const getLatestSnapshotCb = callbackify(getLatestSnapshot)
|
||||||
|
|
||||||
|
export {
|
||||||
|
getFileSnapshotStreamCb as getFileSnapshotStream,
|
||||||
|
getProjectSnapshotCb as getProjectSnapshot,
|
||||||
|
getLatestSnapshotCb as getLatestSnapshot,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const promises = {
|
export const promises = {
|
||||||
getFileSnapshotStream: promisify(getFileSnapshotStream),
|
getFileSnapshotStream,
|
||||||
getProjectSnapshot: promisify(getProjectSnapshot),
|
getProjectSnapshot,
|
||||||
getLatestSnapshot: promisify(getLatestSnapshot),
|
getLatestSnapshot,
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
"@overleaf/settings": "*",
|
"@overleaf/settings": "*",
|
||||||
"async": "^3.2.2",
|
"async": "^3.2.2",
|
||||||
"aws-sdk": "^2.650.0",
|
"aws-sdk": "^2.650.0",
|
||||||
"bluebird": "^3.7.2",
|
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"bunyan": "^1.8.15",
|
"bunyan": "^1.8.15",
|
||||||
"byline": "^4.2.1",
|
"byline": "^4.2.1",
|
||||||
|
|
|
@ -1,20 +1,7 @@
|
||||||
/* 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 sinon from 'sinon'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { strict as esmock } from 'esmock'
|
import { strict as esmock } from 'esmock'
|
||||||
import Core from 'overleaf-editor-core'
|
import Core from 'overleaf-editor-core'
|
||||||
import BPromise from 'bluebird'
|
|
||||||
import * as Errors from '../../../../app/js/Errors.js'
|
import * as Errors from '../../../../app/js/Errors.js'
|
||||||
|
|
||||||
const MODULE_PATH = '../../../../app/js/SnapshotManager.js'
|
const MODULE_PATH = '../../../../app/js/SnapshotManager.js'
|
||||||
|
@ -23,12 +10,16 @@ describe('SnapshotManager', function () {
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.HistoryStoreManager = {
|
this.HistoryStoreManager = {
|
||||||
getBlobStore: sinon.stub(),
|
getBlobStore: sinon.stub(),
|
||||||
getChunkAtVersion: sinon.stub(),
|
promises: {
|
||||||
getMostRecentChunk: sinon.stub(),
|
getChunkAtVersion: sinon.stub(),
|
||||||
getProjectBlobStream: sinon.stub(),
|
getMostRecentChunk: sinon.stub(),
|
||||||
|
getProjectBlobStream: sinon.stub(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
this.WebApiManager = {
|
this.WebApiManager = {
|
||||||
getHistoryId: sinon.stub(),
|
promises: {
|
||||||
|
getHistoryId: sinon.stub(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
this.SnapshotManager = await esmock(MODULE_PATH, {
|
this.SnapshotManager = await esmock(MODULE_PATH, {
|
||||||
'overleaf-editor-core': Core,
|
'overleaf-editor-core': Core,
|
||||||
|
@ -38,12 +29,12 @@ describe('SnapshotManager', function () {
|
||||||
})
|
})
|
||||||
this.projectId = 'project-id-123'
|
this.projectId = 'project-id-123'
|
||||||
this.historyId = 'ol-project-id-123'
|
this.historyId = 'ol-project-id-123'
|
||||||
return (this.callback = sinon.stub())
|
this.callback = sinon.stub()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getFileSnapshotStream', function () {
|
describe('getFileSnapshotStream', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.WebApiManager.getHistoryId.yields(null, this.historyId)
|
this.WebApiManager.promises.getHistoryId.resolves(this.historyId)
|
||||||
this.ranges = {
|
this.ranges = {
|
||||||
comments: [],
|
comments: [],
|
||||||
trackedChanges: [
|
trackedChanges: [
|
||||||
|
@ -65,7 +56,7 @@ describe('SnapshotManager', function () {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
this.HistoryStoreManager.getChunkAtVersion.yields(null, {
|
this.HistoryStoreManager.promises.getChunkAtVersion.resolves({
|
||||||
chunk: {
|
chunk: {
|
||||||
history: {
|
history: {
|
||||||
snapshot: {
|
snapshot: {
|
||||||
|
@ -121,7 +112,7 @@ describe('SnapshotManager', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('of a text file with no tracked changes', function () {
|
describe('of a text file with no tracked changes', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(async function () {
|
||||||
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
||||||
getString: (this.getString = sinon.stub().resolves(
|
getString: (this.getString = sinon.stub().resolves(
|
||||||
`\
|
`\
|
||||||
|
@ -134,37 +125,33 @@ Four five six\
|
||||||
)),
|
)),
|
||||||
getObject: sinon.stub().rejects(),
|
getObject: sinon.stub().rejects(),
|
||||||
})
|
})
|
||||||
this.SnapshotManager.getFileSnapshotStream(
|
this.stream = await this.SnapshotManager.promises.getFileSnapshotStream(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
5,
|
5,
|
||||||
'main.tex',
|
'main.tex'
|
||||||
(error, stream) => {
|
|
||||||
this.stream = stream
|
|
||||||
return done(error)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the overleaf id', function () {
|
it('should get the overleaf id', function () {
|
||||||
return this.WebApiManager.getHistoryId
|
this.WebApiManager.promises.getHistoryId
|
||||||
.calledWith(this.projectId)
|
.calledWith(this.projectId)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the chunk', function () {
|
it('should get the chunk', function () {
|
||||||
return this.HistoryStoreManager.getChunkAtVersion
|
this.HistoryStoreManager.promises.getChunkAtVersion
|
||||||
.calledWith(this.projectId, this.historyId, 5)
|
.calledWith(this.projectId, this.historyId, 5)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the blob of the starting snapshot', function () {
|
it('should get the blob of the starting snapshot', function () {
|
||||||
return this.getString
|
this.getString
|
||||||
.calledWith('35c9bd86574d61dcadbce2fdd3d4a0684272c6ea')
|
.calledWith('35c9bd86574d61dcadbce2fdd3d4a0684272c6ea')
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return a string stream with the text content', function () {
|
it('should return a string stream with the text content', function () {
|
||||||
return expect(this.stream.read().toString()).to.equal(
|
expect(this.stream.read().toString()).to.equal(
|
||||||
`\
|
`\
|
||||||
Hello world
|
Hello world
|
||||||
|
|
||||||
|
@ -188,48 +175,41 @@ Seven eight nine\
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call back with error', function (done) {
|
it('should call back with error', async function () {
|
||||||
this.SnapshotManager.getFileSnapshotStream(
|
await expect(
|
||||||
this.projectId,
|
this.SnapshotManager.promises.getFileSnapshotStream(
|
||||||
5,
|
this.projectId,
|
||||||
'main.tex',
|
5,
|
||||||
error => {
|
'main.tex'
|
||||||
expect(error).to.exist
|
)
|
||||||
expect(error.name).to.equal(this.error.name)
|
).to.be.rejectedWith(this.error)
|
||||||
done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('of a text file with tracked changes', function () {
|
describe('of a text file with tracked changes', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(async function () {
|
||||||
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
||||||
getString: (this.getString = sinon
|
getString: (this.getString = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.resolves('the quick brown fox jumps over the lazy dog')),
|
.resolves('the quick brown fox jumps over the lazy dog')),
|
||||||
getObject: (this.getObject = sinon.stub().resolves(this.ranges)),
|
getObject: (this.getObject = sinon.stub().resolves(this.ranges)),
|
||||||
})
|
})
|
||||||
this.SnapshotManager.getFileSnapshotStream(
|
this.stream = await this.SnapshotManager.promises.getFileSnapshotStream(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
5,
|
5,
|
||||||
'file_with_ranges.tex',
|
'file_with_ranges.tex'
|
||||||
(error, stream) => {
|
|
||||||
this.stream = stream
|
|
||||||
done(error)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the overleaf id', function () {
|
it('should get the overleaf id', function () {
|
||||||
this.WebApiManager.getHistoryId
|
this.WebApiManager.promises.getHistoryId
|
||||||
.calledWith(this.projectId)
|
.calledWith(this.projectId)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the chunk', function () {
|
it('should get the chunk', function () {
|
||||||
this.HistoryStoreManager.getChunkAtVersion
|
this.HistoryStoreManager.promises.getChunkAtVersion
|
||||||
.calledWith(this.projectId, this.historyId, 5)
|
.calledWith(this.projectId, this.historyId, 5)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
@ -254,35 +234,32 @@ Seven eight nine\
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('of a binary file', function () {
|
describe('of a binary file', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(async function () {
|
||||||
this.HistoryStoreManager.getProjectBlobStream
|
this.HistoryStoreManager.promises.getProjectBlobStream
|
||||||
.withArgs(this.historyId)
|
.withArgs(this.historyId)
|
||||||
.yields(null, (this.stream = 'mock-stream'))
|
.resolves((this.stream = 'mock-stream'))
|
||||||
return this.SnapshotManager.getFileSnapshotStream(
|
this.returnedStream =
|
||||||
this.projectId,
|
await this.SnapshotManager.promises.getFileSnapshotStream(
|
||||||
5,
|
this.projectId,
|
||||||
'binary.png',
|
5,
|
||||||
(error, returnedStream) => {
|
'binary.png'
|
||||||
this.returnedStream = returnedStream
|
)
|
||||||
return done(error)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the overleaf id', function () {
|
it('should get the overleaf id', function () {
|
||||||
return this.WebApiManager.getHistoryId
|
this.WebApiManager.promises.getHistoryId
|
||||||
.calledWith(this.projectId)
|
.calledWith(this.projectId)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the chunk', function () {
|
it('should get the chunk', function () {
|
||||||
return this.HistoryStoreManager.getChunkAtVersion
|
this.HistoryStoreManager.promises.getChunkAtVersion
|
||||||
.calledWith(this.projectId, this.historyId, 5)
|
.calledWith(this.projectId, this.historyId, 5)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the blob of the starting snapshot', function () {
|
it('should get the blob of the starting snapshot', function () {
|
||||||
return this.HistoryStoreManager.getProjectBlobStream
|
this.HistoryStoreManager.promises.getProjectBlobStream
|
||||||
.calledWith(
|
.calledWith(
|
||||||
this.historyId,
|
this.historyId,
|
||||||
'c6654ea913979e13e22022653d284444f284a172'
|
'c6654ea913979e13e22022653d284444f284a172'
|
||||||
|
@ -290,36 +267,27 @@ Seven eight nine\
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
return it('should return a stream with the blob content', function () {
|
it('should return a stream with the blob content', function () {
|
||||||
return expect(this.returnedStream).to.equal(this.stream)
|
expect(this.returnedStream).to.equal(this.stream)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return describe("when the file doesn't exist", function () {
|
describe("when the file doesn't exist", function () {
|
||||||
beforeEach(function (done) {
|
it('should return a NotFoundError', async function () {
|
||||||
return this.SnapshotManager.getFileSnapshotStream(
|
await expect(
|
||||||
this.projectId,
|
this.SnapshotManager.promises.getFileSnapshotStream(
|
||||||
5,
|
this.projectId,
|
||||||
'not-here.png',
|
5,
|
||||||
(error, returnedStream) => {
|
'not-here.png'
|
||||||
this.error = error
|
)
|
||||||
this.returnedStream = returnedStream
|
).to.be.rejectedWith(Errors.NotFoundError)
|
||||||
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 () {
|
describe('getProjectSnapshot', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.WebApiManager.getHistoryId.yields(null, this.historyId)
|
this.WebApiManager.promises.getHistoryId.resolves(this.historyId)
|
||||||
this.ranges = {
|
this.ranges = {
|
||||||
comments: [],
|
comments: [],
|
||||||
trackedChanges: [
|
trackedChanges: [
|
||||||
|
@ -341,7 +309,7 @@ Seven eight nine\
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
return this.HistoryStoreManager.getChunkAtVersion.yields(null, {
|
this.HistoryStoreManager.promises.getChunkAtVersion.resolves({
|
||||||
chunk: (this.chunk = {
|
chunk: (this.chunk = {
|
||||||
history: {
|
history: {
|
||||||
snapshot: {
|
snapshot: {
|
||||||
|
@ -416,7 +384,7 @@ Seven eight nine\
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('of project', function () {
|
describe('of project', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(async function () {
|
||||||
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
this.HistoryStoreManager.getBlobStore.withArgs(this.historyId).returns({
|
||||||
getString: (this.getString = sinon.stub().resolves(
|
getString: (this.getString = sinon.stub().resolves(
|
||||||
`\
|
`\
|
||||||
|
@ -429,24 +397,20 @@ Four five six\
|
||||||
)),
|
)),
|
||||||
getObject: (this.getObject = sinon.stub().resolves(this.ranges)),
|
getObject: (this.getObject = sinon.stub().resolves(this.ranges)),
|
||||||
})
|
})
|
||||||
this.SnapshotManager.getProjectSnapshot(
|
this.data = await this.SnapshotManager.promises.getProjectSnapshot(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
6,
|
6
|
||||||
(error, data) => {
|
|
||||||
this.data = data
|
|
||||||
done(error)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the overleaf id', function () {
|
it('should get the overleaf id', function () {
|
||||||
this.WebApiManager.getHistoryId
|
this.WebApiManager.promises.getHistoryId
|
||||||
.calledWith(this.projectId)
|
.calledWith(this.projectId)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the chunk', function () {
|
it('should get the chunk', function () {
|
||||||
this.HistoryStoreManager.getChunkAtVersion
|
this.HistoryStoreManager.promises.getChunkAtVersion
|
||||||
.calledWith(this.projectId, this.historyId, 6)
|
.calledWith(this.projectId, this.historyId, 6)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
@ -507,21 +471,18 @@ Four five six\
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call back with error', function (done) {
|
it('should call back with error', async function () {
|
||||||
this.SnapshotManager.getProjectSnapshot(this.projectId, 5, error => {
|
expect(
|
||||||
expect(error).to.exist
|
this.SnapshotManager.promises.getProjectSnapshot(this.projectId, 5)
|
||||||
expect(error.message).to.equal(this.error.message)
|
).to.be.rejectedWith(this.error.message)
|
||||||
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return describe('getLatestSnapshot', function () {
|
describe('getLatestSnapshot', function () {
|
||||||
describe('for a project', function () {
|
describe('for a project', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(async function () {
|
||||||
this.HistoryStoreManager.getMostRecentChunk.yields(null, {
|
this.HistoryStoreManager.promises.getMostRecentChunk.resolves({
|
||||||
chunk: (this.chunk = {
|
chunk: (this.chunk = {
|
||||||
history: {
|
history: {
|
||||||
snapshot: {
|
snapshot: {
|
||||||
|
@ -582,57 +543,39 @@ Four five six\
|
||||||
)),
|
)),
|
||||||
getObject: sinon.stub().rejects(),
|
getObject: sinon.stub().rejects(),
|
||||||
})
|
})
|
||||||
this.SnapshotManager.getLatestSnapshot(
|
this.data = await this.SnapshotManager.promises.getLatestSnapshot(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.historyId,
|
this.historyId
|
||||||
(error, data) => {
|
|
||||||
this.data = data
|
|
||||||
done(error)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the chunk', function () {
|
it('should get the chunk', function () {
|
||||||
return this.HistoryStoreManager.getMostRecentChunk
|
this.HistoryStoreManager.promises.getMostRecentChunk
|
||||||
.calledWith(this.projectId, this.historyId)
|
.calledWith(this.projectId, this.historyId)
|
||||||
.should.equal(true)
|
.should.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
return it('should produce the snapshot file data', function () {
|
it('should produce the snapshot file data', function () {
|
||||||
expect(this.data).to.have.all.keys(['main.tex', 'binary.png'])
|
expect(this.data).to.have.all.keys(['main.tex', 'binary.png'])
|
||||||
expect(this.data['main.tex']).to.exist
|
expect(this.data['main.tex']).to.exist
|
||||||
expect(this.data['binary.png']).to.exist
|
expect(this.data['binary.png']).to.exist
|
||||||
expect(this.data['main.tex'].getStringLength()).to.equal(59)
|
expect(this.data['main.tex'].getStringLength()).to.equal(59)
|
||||||
expect(this.data['binary.png'].getByteLength()).to.equal(41)
|
expect(this.data['binary.png'].getByteLength()).to.equal(41)
|
||||||
return expect(this.data['binary.png'].getHash()).to.equal(
|
expect(this.data['binary.png'].getHash()).to.equal(
|
||||||
'c6654ea913979e13e22022653d284444f284a172'
|
'c6654ea913979e13e22022653d284444f284a172'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return describe('when the chunk is empty', function () {
|
describe('when the chunk is empty', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(async function () {
|
||||||
this.HistoryStoreManager.getMostRecentChunk.yields(null)
|
this.HistoryStoreManager.promises.getMostRecentChunk.resolves(null)
|
||||||
return this.SnapshotManager.getLatestSnapshot(
|
expect(
|
||||||
this.projectId,
|
this.SnapshotManager.promises.getLatestSnapshot(
|
||||||
this.historyId,
|
this.projectId,
|
||||||
(error, data) => {
|
this.historyId
|
||||||
this.error = error
|
)
|
||||||
this.data = data
|
).to.be.rejectedWith('undefined chunk')
|
||||||
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')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue