mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-10 06:39:01 +02:00
Merge pull request #22533 from overleaf/ar-only-use-history-for-blobs-when-enabled
[web] only use history for blobs when enabled GitOrigin-RevId: 010983e9b29657d4c594e03945dca5700577bf0a
This commit is contained in:
@@ -15,6 +15,7 @@ const { Cookie } = require('tough-cookie')
|
||||
const ClsiCookieManager = require('./ClsiCookieManager')(
|
||||
Settings.apis.clsi?.backendGroupName
|
||||
)
|
||||
const Features = require('../../infrastructure/Features')
|
||||
const NewBackendCloudClsiCookieManager = require('./ClsiCookieManager')(
|
||||
Settings.apis.clsi_new?.backendGroupName
|
||||
)
|
||||
@@ -744,7 +745,7 @@ function _finaliseRequest(projectId, options, project, docs, files) {
|
||||
const filestoreURL = `${Settings.apis.filestore.url}/project/${project._id}/file/${file._id}`
|
||||
let url = filestoreURL
|
||||
let fallbackURL
|
||||
if (file.hash) {
|
||||
if (file.hash && Features.hasFeature('project-history-blobs')) {
|
||||
const { bucket, key } = getBlobLocation(historyId, file.hash)
|
||||
url = `${Settings.apis.filestore.url}/bucket/${bucket}/key/${key}`
|
||||
fallbackURL = filestoreURL
|
||||
|
||||
@@ -4,6 +4,8 @@ import logger from '@overleaf/logger'
|
||||
import ProjectEntityHandler from '../Project/ProjectEntityHandler.js'
|
||||
import ProjectGetter from '../Project/ProjectGetter.js'
|
||||
import HistoryManager from '../History/HistoryManager.js'
|
||||
import FileStoreHandler from '../FileStore/FileStoreHandler.js'
|
||||
import Features from '../../infrastructure/Features.js'
|
||||
let ProjectZipStreamManager
|
||||
|
||||
export default ProjectZipStreamManager = {
|
||||
@@ -108,17 +110,35 @@ export default ProjectZipStreamManager = {
|
||||
})
|
||||
},
|
||||
|
||||
getFileStream: (projectId, file, callback) => {
|
||||
if (Features.hasFeature('project-history-blobs')) {
|
||||
HistoryManager.requestBlobWithFallback(
|
||||
projectId,
|
||||
file.hash,
|
||||
file._id,
|
||||
(error, result) => {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
const { stream } = result
|
||||
callback(null, stream)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
FileStoreHandler.getFileStream(projectId, file._id, {}, callback)
|
||||
}
|
||||
},
|
||||
|
||||
addAllFilesToArchive(projectId, archive, callback) {
|
||||
ProjectEntityHandler.getAllFiles(projectId, (error, files) => {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
const jobs = Object.entries(files).map(([path, file]) => cb => {
|
||||
HistoryManager.requestBlobWithFallback(
|
||||
ProjectZipStreamManager.getFileStream(
|
||||
projectId,
|
||||
file.hash,
|
||||
file._id,
|
||||
(error, result) => {
|
||||
file,
|
||||
(error, stream) => {
|
||||
if (error) {
|
||||
logger.warn(
|
||||
{ err: error, projectId, fileId: file._id },
|
||||
@@ -129,7 +149,6 @@ export default ProjectZipStreamManager = {
|
||||
if (path[0] === '/') {
|
||||
path = path.slice(1)
|
||||
}
|
||||
const { stream } = result
|
||||
archive.append(stream, { name: path })
|
||||
stream.on('end', () => cb())
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const { File } = require('../../models/File')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const OError = require('@overleaf/o-error')
|
||||
const { promisifyAll } = require('@overleaf/promise-utils')
|
||||
const Features = require('../../infrastructure/Features')
|
||||
|
||||
const ONE_MIN_IN_MS = 60 * 1000
|
||||
const FIVE_MINS_IN_MS = ONE_MIN_IN_MS * 5
|
||||
@@ -38,6 +39,28 @@ const FileStoreHandler = {
|
||||
})
|
||||
},
|
||||
|
||||
_uploadToHistory(historyId, hash, size, fsPath, callback) {
|
||||
if (Features.hasFeature('project-history-blobs')) {
|
||||
Async.retry(
|
||||
FileStoreHandler.RETRY_ATTEMPTS,
|
||||
cb =>
|
||||
HistoryManager.uploadBlobFromDisk(historyId, hash, size, fsPath, cb),
|
||||
error => callback(error, true)
|
||||
)
|
||||
} else {
|
||||
callback(null, false)
|
||||
}
|
||||
},
|
||||
|
||||
_uploadToFileStore(projectId, fileArgs, fsPath, callback) {
|
||||
Async.retry(
|
||||
FileStoreHandler.RETRY_ATTEMPTS,
|
||||
(cb, results) =>
|
||||
FileStoreHandler._doUploadFileFromDisk(projectId, fileArgs, fsPath, cb),
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
uploadFileFromDiskWithHistoryId(
|
||||
projectId,
|
||||
historyId,
|
||||
@@ -68,30 +91,20 @@ const FileStoreHandler = {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
Async.retry(
|
||||
FileStoreHandler.RETRY_ATTEMPTS,
|
||||
cb =>
|
||||
HistoryManager.uploadBlobFromDisk(
|
||||
historyId,
|
||||
hash,
|
||||
stat.size,
|
||||
fsPath,
|
||||
cb
|
||||
),
|
||||
function (err) {
|
||||
FileStoreHandler._uploadToHistory(
|
||||
historyId,
|
||||
hash,
|
||||
stat.size,
|
||||
fsPath,
|
||||
function (err, createdBlob) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
fileArgs = { ...fileArgs, hash }
|
||||
Async.retry(
|
||||
FileStoreHandler.RETRY_ATTEMPTS,
|
||||
(cb, results) =>
|
||||
FileStoreHandler._doUploadFileFromDisk(
|
||||
projectId,
|
||||
fileArgs,
|
||||
fsPath,
|
||||
cb
|
||||
),
|
||||
FileStoreHandler._uploadToFileStore(
|
||||
projectId,
|
||||
fileArgs,
|
||||
fsPath,
|
||||
function (err, result) {
|
||||
if (err) {
|
||||
OError.tag(err, 'Error uploading file, retries failed', {
|
||||
@@ -100,7 +113,7 @@ const FileStoreHandler = {
|
||||
})
|
||||
return callback(err)
|
||||
}
|
||||
callback(err, result.url, result.fileRef, true)
|
||||
callback(err, result.url, result.fileRef, createdBlob)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -159,7 +172,7 @@ const FileStoreHandler = {
|
||||
const historyId = project.overleaf?.history?.id
|
||||
const fileId = file._id
|
||||
const hash = file.hash
|
||||
if (historyId && hash) {
|
||||
if (historyId && hash && Features.hasFeature('project-history-blobs')) {
|
||||
// new behaviour - request from history
|
||||
const range = _extractRange(query?.range)
|
||||
HistoryManager.requestBlobWithFallback(
|
||||
|
||||
@@ -77,6 +77,9 @@ module.exports = HistoryController = {
|
||||
* has a hash.
|
||||
* */
|
||||
fileToBlobRedirectMiddleware(req, res, next) {
|
||||
if (!Features.hasFeature('project-history-blobs')) {
|
||||
return next()
|
||||
}
|
||||
const projectId = req.params.Project_id
|
||||
const fileId = req.params.File_id
|
||||
ProjectLocator.findElement(
|
||||
|
||||
@@ -7,7 +7,8 @@ function projectHistoryURLWithFilestoreFallback(
|
||||
origin
|
||||
) {
|
||||
const filestoreURL = `${Settings.apis.filestore.url}/project/${projectId}/file/${fileRef._id}?from=${origin}`
|
||||
if (fileRef.hash) {
|
||||
// TODO: When this file is converted to ES modules we will be able to use Features.hasFeature('project-history-blobs'). Currently we can't stub the feature return value in tests.
|
||||
if (fileRef.hash && Settings.enableProjectHistoryBlobs) {
|
||||
return {
|
||||
url: `${Settings.apis.project_history.url}/project/${historyId}/blob/${fileRef.hash}`,
|
||||
fallbackURL: filestoreURL,
|
||||
|
||||
@@ -800,6 +800,9 @@ const _ProjectController = {
|
||||
isInvitedMember
|
||||
),
|
||||
chatEnabled: Features.hasFeature('chat'),
|
||||
projectHistoryBlobsEnabled: Features.hasFeature(
|
||||
'project-history-blobs'
|
||||
),
|
||||
roMirrorOnClientNoLocalStorage:
|
||||
Settings.adminOnlyLogin || project.name.startsWith('Debug: '),
|
||||
languages: Settings.languages,
|
||||
|
||||
@@ -213,7 +213,8 @@ async function _copyFiles(sourceEntries, sourceProject, targetProject) {
|
||||
file.hash = sourceFile.hash
|
||||
}
|
||||
let createdBlob = false
|
||||
if (file.hash != null) {
|
||||
if (file.hash != null && Features.hasFeature('project-history-blobs')) {
|
||||
const usingFilestore = Features.hasFeature('filestore')
|
||||
try {
|
||||
await HistoryManager.promises.copyBlob(
|
||||
sourceHistoryId,
|
||||
@@ -221,20 +222,32 @@ async function _copyFiles(sourceEntries, sourceProject, targetProject) {
|
||||
file.hash
|
||||
)
|
||||
createdBlob = true
|
||||
if (!usingFilestore) {
|
||||
return { createdBlob, file, path, url: null }
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
{
|
||||
err,
|
||||
if (!usingFilestore) {
|
||||
throw OError.tag(err, 'unexpected error copying blob', {
|
||||
sourceProjectId: sourceProject._id,
|
||||
targetProjectId: targetProject._id,
|
||||
sourceFile,
|
||||
sourceHistoryId,
|
||||
},
|
||||
'unexpected error copying blob'
|
||||
)
|
||||
})
|
||||
} else {
|
||||
logger.error(
|
||||
{
|
||||
err,
|
||||
sourceProjectId: sourceProject._id,
|
||||
targetProjectId: targetProject._id,
|
||||
sourceFile,
|
||||
sourceHistoryId,
|
||||
},
|
||||
'unexpected error copying blob'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (createdBlob && Features.hasFeature('saas')) {
|
||||
if (createdBlob && Features.hasFeature('project-history-blobs')) {
|
||||
return { createdBlob, file, path, url: null }
|
||||
}
|
||||
const url = await FileStoreHandler.promises.copyFile(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
let ProjectEditorHandler
|
||||
const _ = require('lodash')
|
||||
const Path = require('path')
|
||||
const Features = require('../../infrastructure/Features')
|
||||
|
||||
function mergeDeletedDocs(a, b) {
|
||||
const docIdsInA = new Set(a.map(doc => doc._id.toString()))
|
||||
@@ -123,12 +124,18 @@ module.exports = ProjectEditorHandler = {
|
||||
},
|
||||
|
||||
buildFileModelView(file) {
|
||||
const additionalFileProperties = {}
|
||||
|
||||
if (Features.hasFeature('project-history-blobs')) {
|
||||
additionalFileProperties.hash = file.hash
|
||||
}
|
||||
|
||||
return {
|
||||
_id: file._id,
|
||||
name: file.name,
|
||||
linkedFileData: file.linkedFileData,
|
||||
created: file.created,
|
||||
hash: file.hash,
|
||||
...additionalFileProperties,
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ const trackChangesModuleAvailable =
|
||||
* @property {boolean | undefined} enableGithubSync
|
||||
* @property {boolean | undefined} enableGitBridge
|
||||
* @property {boolean | undefined} enableHomepage
|
||||
* @property {boolean | undefined} enableProjectHistoryBlobs
|
||||
* @property {boolean | undefined} disableFilestore
|
||||
* @property {boolean | undefined} enableSaml
|
||||
* @property {boolean | undefined} ldap
|
||||
* @property {boolean | undefined} oauth
|
||||
@@ -86,6 +88,10 @@ const Features = {
|
||||
_.get(Settings, ['apis', 'linkedUrlProxy', 'url']) &&
|
||||
Settings.enabledLinkedFileTypes.includes('url')
|
||||
)
|
||||
case 'project-history-blobs':
|
||||
return Boolean(Settings.enableProjectHistoryBlobs)
|
||||
case 'filestore':
|
||||
return Boolean(Settings.disableFilestore) === false
|
||||
case 'support':
|
||||
return supportModuleAvailable
|
||||
case 'symbol-palette':
|
||||
|
||||
@@ -10,6 +10,7 @@ meta(name="ol-isRestrictedTokenMember" data-type="boolean" content=isRestrictedT
|
||||
meta(name="ol-maxDocLength" data-type="json" content=maxDocLength)
|
||||
meta(name="ol-wikiEnabled" data-type="boolean" content=settings.proxyLearn)
|
||||
meta(name="ol-chatEnabled" data-type="boolean" content=chatEnabled)
|
||||
meta(name="ol-projectHistoryBlobsEnabled" data-type="boolean" content=projectHistoryBlobsEnabled)
|
||||
meta(name="ol-gitBridgePublicBaseUrl" content=gitBridgePublicBaseUrl)
|
||||
meta(name="ol-gitBridgeEnabled" data-type="boolean" content=gitBridgeEnabled)
|
||||
meta(name="ol-compilesUserContentDomain" content=settings.compilesUserContentDomain)
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
import { Folder } from '../../../../../types/folder'
|
||||
import { useReferencesContext } from '@/features/ide-react/context/references-context'
|
||||
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
|
||||
import { fileUrl } from '@/features/utils/fileUrl'
|
||||
|
||||
type DroppedFile = File & {
|
||||
relativePath?: string
|
||||
@@ -494,7 +495,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
|
||||
const selectedEntity = findInTree(fileTreeData, selectedEntityId)
|
||||
|
||||
if (selectedEntity?.type === 'fileRef') {
|
||||
return `/project/${projectId}/blob/${selectedEntity.entity.hash}?fallback=${selectedEntityId}`
|
||||
return fileUrl(projectId, selectedEntityId, selectedEntity.entity.hash)
|
||||
}
|
||||
|
||||
if (selectedEntity?.type === 'doc') {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// Helper function to compute the url for a file in history-v1 or filestore.
|
||||
// This will be obsolete when the migration to history-v1 is complete.
|
||||
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
const projectHistoryBlobsEnabled = getMeta('ol-projectHistoryBlobsEnabled')
|
||||
|
||||
export function fileUrl(projectId, id, hash) {
|
||||
if (hash) {
|
||||
if (projectHistoryBlobsEnabled && hash) {
|
||||
return `/project/${projectId}/blob/${hash}?fallback=${id}`
|
||||
} else {
|
||||
return `/project/${projectId}/file/${id}`
|
||||
|
||||
@@ -165,6 +165,7 @@ export interface Meta {
|
||||
'ol-prefetchedProjectsBlob': GetProjectsResponseBody | undefined
|
||||
'ol-primaryEmail': { email: string; confirmed: boolean }
|
||||
'ol-project': any // TODO
|
||||
'ol-projectHistoryBlobsEnabled': boolean
|
||||
'ol-projectSyncSuccessMessage': string
|
||||
'ol-projectTags': Tag[]
|
||||
'ol-project_id': string
|
||||
|
||||
@@ -257,7 +257,7 @@ describe('ProjectStructureChanges', function () {
|
||||
expect(updates[2].type).to.equal('add-file')
|
||||
expect(updates[2].userId).to.equal(owner._id)
|
||||
expect(updates[2].pathname).to.equal('/frog.jpg')
|
||||
if (Features.hasFeature('saas')) {
|
||||
if (Features.hasFeature('project-history-blobs')) {
|
||||
expect(updates[2].url).to.be.null
|
||||
} else {
|
||||
expect(updates[2].url).to.be.a('string')
|
||||
|
||||
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import sinon from 'sinon'
|
||||
import logger from '@overleaf/logger'
|
||||
import Metrics from './helpers/metrics.js'
|
||||
import Features from '../../../app/src/infrastructure/Features.js'
|
||||
const User = UserHelper.promises
|
||||
|
||||
let MockV1HistoryApi, MockFilestoreApi
|
||||
@@ -75,41 +76,45 @@ describe('HistoryTests', function () {
|
||||
afterEach(function () {
|
||||
spy.restore()
|
||||
})
|
||||
it('should work from history-v1', async function () {
|
||||
const { response, body } = await user.doRequest('GET', downloadZIPURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.include('2pixel.png')
|
||||
await expectHistoryV1Hit()
|
||||
})
|
||||
it('should work from filestore', async function () {
|
||||
MockV1HistoryApi.reset()
|
||||
const { response, body } = await user.doRequest('GET', downloadZIPURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.include('2pixel.png')
|
||||
await expectFilestoreHit()
|
||||
})
|
||||
it('should not include when missing in both places', async function () {
|
||||
MockFilestoreApi.reset()
|
||||
MockV1HistoryApi.reset()
|
||||
const { response, body } = await user.doRequest('GET', downloadZIPURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(
|
||||
spy.args.find(([, msg]) => msg === 'error adding files to zip stream')
|
||||
).to.exist
|
||||
expect(body).to.not.include('2pixel.png')
|
||||
await expectNoIncrement()
|
||||
})
|
||||
if (Features.hasFeature('project-history-blobs')) {
|
||||
it('should work from history-v1', async function () {
|
||||
const { response, body } = await user.doRequest('GET', downloadZIPURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.include('2pixel.png')
|
||||
await expectHistoryV1Hit()
|
||||
})
|
||||
it('should work from filestore', async function () {
|
||||
MockV1HistoryApi.reset()
|
||||
const { response, body } = await user.doRequest('GET', downloadZIPURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(body).to.include('2pixel.png')
|
||||
await expectFilestoreHit()
|
||||
})
|
||||
it('should not include when missing in both places', async function () {
|
||||
MockFilestoreApi.reset()
|
||||
MockV1HistoryApi.reset()
|
||||
const { response, body } = await user.doRequest('GET', downloadZIPURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(
|
||||
spy.args.find(([, msg]) => msg === 'error adding files to zip stream')
|
||||
).to.exist
|
||||
expect(body).to.not.include('2pixel.png')
|
||||
await expectNoIncrement()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('/project/:projectId/blob/:hash', function () {
|
||||
describe('HEAD', function () {
|
||||
it('should fetch the file size from history-v1', async function () {
|
||||
const { response } = await user.doRequest('HEAD', fileURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(response.headers['x-served-by']).to.include('history-v1')
|
||||
expect(response.headers['content-length']).to.equal('3694')
|
||||
await expectHistoryV1Hit()
|
||||
})
|
||||
if (Features.hasFeature('project-history-blobs')) {
|
||||
it('should fetch the file size from history-v1', async function () {
|
||||
const { response } = await user.doRequest('HEAD', fileURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(response.headers['x-served-by']).to.include('history-v1')
|
||||
expect(response.headers['content-length']).to.equal('3694')
|
||||
await expectHistoryV1Hit()
|
||||
})
|
||||
}
|
||||
it('should return 404 without fallback', async function () {
|
||||
MockV1HistoryApi.reset()
|
||||
const { response } = await user.doRequest('HEAD', fileURL)
|
||||
@@ -133,13 +138,15 @@ describe('HistoryTests', function () {
|
||||
})
|
||||
})
|
||||
describe('GET', function () {
|
||||
it('should fetch the file from history-v1', async function () {
|
||||
const { response, body } = await user.doRequest('GET', fileURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(response.headers['x-served-by']).to.include('history-v1')
|
||||
expect(body).to.equal(fileContent)
|
||||
await expectHistoryV1Hit()
|
||||
})
|
||||
if (Features.hasFeature('project-history-blobs')) {
|
||||
it('should fetch the file from history-v1', async function () {
|
||||
const { response, body } = await user.doRequest('GET', fileURL)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(response.headers['x-served-by']).to.include('history-v1')
|
||||
expect(body).to.equal(fileContent)
|
||||
await expectHistoryV1Hit()
|
||||
})
|
||||
}
|
||||
it('should return 404 without fallback', async function () {
|
||||
MockV1HistoryApi.reset()
|
||||
const { response } = await user.doRequest('GET', fileURL)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export function resetMeta() {
|
||||
window.metaAttributesCache = new Map()
|
||||
window.metaAttributesCache.set('ol-projectHistoryBlobsEnabled', true)
|
||||
window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' })
|
||||
window.metaAttributesCache.set('ol-ExposedSettings', {
|
||||
appName: 'Overleaf',
|
||||
|
||||
@@ -144,6 +144,9 @@ describe('ClsiManager', function () {
|
||||
enablePdfCaching: true,
|
||||
clsiCookie: { key: 'clsiserver' },
|
||||
}
|
||||
this.Features = {
|
||||
hasFeature: sinon.stub().withArgs('project-history-blobs').returns(true),
|
||||
}
|
||||
this.HistoryManager = {
|
||||
getBlobLocation: sinon.stub().callsFake((historyId, hash) => {
|
||||
if (hash === GLOBAL_BLOB_HASH) {
|
||||
@@ -162,6 +165,7 @@ describe('ClsiManager', function () {
|
||||
'../../models/Project': {
|
||||
Project: this.Project,
|
||||
},
|
||||
'../../infrastructure/Features': this.Features,
|
||||
'../Project/ProjectEntityHandler': this.ProjectEntityHandler,
|
||||
'../Project/ProjectGetter': this.ProjectGetter,
|
||||
'../DocumentUpdater/DocumentUpdaterHandler':
|
||||
|
||||
@@ -27,6 +27,7 @@ describe('ProjectZipStreamManager', function () {
|
||||
info: sinon.stub(),
|
||||
debug: sinon.stub(),
|
||||
}
|
||||
|
||||
return (this.ProjectZipStreamManager = await esmock.strict(modulePath, {
|
||||
archiver: (this.archiver = sinon.stub().returns(this.archive)),
|
||||
'@overleaf/logger': this.logger,
|
||||
@@ -36,6 +37,14 @@ describe('ProjectZipStreamManager', function () {
|
||||
(this.HistoryManager = {}),
|
||||
'../../../../app/src/Features/Project/ProjectGetter':
|
||||
(this.ProjectGetter = {}),
|
||||
'../../../../app/src/Features/FileStore/FileStoreHandler':
|
||||
(this.FileStoreHandler = {}),
|
||||
'../../../../app/src/infrastructure/Features': (this.Features = {
|
||||
hasFeature: sinon
|
||||
.stub()
|
||||
.withArgs('project-history-blobs')
|
||||
.returns(true),
|
||||
}),
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -380,65 +389,89 @@ describe('ProjectZipStreamManager', function () {
|
||||
this.ProjectEntityHandler.getAllFiles = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.files)
|
||||
this.HistoryManager.requestBlobWithFallback = (
|
||||
projectId,
|
||||
hash,
|
||||
fileId,
|
||||
callback
|
||||
) => {
|
||||
return callback(null, { stream: this.streams[fileId] })
|
||||
}
|
||||
sinon.spy(this.HistoryManager, 'requestBlobWithFallback')
|
||||
this.ProjectZipStreamManager.addAllFilesToArchive(
|
||||
this.project_id,
|
||||
this.archive,
|
||||
this.callback
|
||||
)
|
||||
return (() => {
|
||||
const result = []
|
||||
})
|
||||
describe('with project-history-blobs feature enabled', function () {
|
||||
beforeEach(function () {
|
||||
this.HistoryManager.requestBlobWithFallback = (
|
||||
projectId,
|
||||
hash,
|
||||
fileId,
|
||||
callback
|
||||
) => {
|
||||
return callback(null, { stream: this.streams[fileId] })
|
||||
}
|
||||
sinon.spy(this.HistoryManager, 'requestBlobWithFallback')
|
||||
this.ProjectZipStreamManager.addAllFilesToArchive(
|
||||
this.project_id,
|
||||
this.archive,
|
||||
this.callback
|
||||
)
|
||||
for (const path in this.streams) {
|
||||
const stream = this.streams[path]
|
||||
result.push(stream.emit('end'))
|
||||
stream.emit('end')
|
||||
}
|
||||
return result
|
||||
})()
|
||||
})
|
||||
})
|
||||
|
||||
it('should get the files for the project', function () {
|
||||
return this.ProjectEntityHandler.getAllFiles
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
it('should get the files for the project', function () {
|
||||
return this.ProjectEntityHandler.getAllFiles
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should get a stream for each file', function () {
|
||||
return (() => {
|
||||
const result = []
|
||||
it('should get a stream for each file', function () {
|
||||
for (const path in this.files) {
|
||||
const file = this.files[path]
|
||||
result.push(
|
||||
this.HistoryManager.requestBlobWithFallback
|
||||
.calledWith(this.project_id, file.hash, file._id)
|
||||
.should.equal(true)
|
||||
)
|
||||
}
|
||||
return result
|
||||
})()
|
||||
})
|
||||
|
||||
it('should add each file to the archive', function () {
|
||||
return (() => {
|
||||
const result = []
|
||||
this.HistoryManager.requestBlobWithFallback
|
||||
.calledWith(this.project_id, file.hash, file._id)
|
||||
.should.equal(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('should add each file to the archive', function () {
|
||||
for (let path in this.files) {
|
||||
const file = this.files[path]
|
||||
path = path.slice(1) // remove "/"
|
||||
result.push(
|
||||
this.archive.append
|
||||
.calledWith(this.streams[file._id], { name: path })
|
||||
.should.equal(true)
|
||||
)
|
||||
this.archive.append
|
||||
.calledWith(this.streams[file._id], { name: path })
|
||||
.should.equal(true)
|
||||
}
|
||||
return result
|
||||
})()
|
||||
})
|
||||
})
|
||||
|
||||
describe('with project-history-blobs feature disabled', function () {
|
||||
beforeEach(function () {
|
||||
this.FileStoreHandler.getFileStream = (
|
||||
projectId,
|
||||
fileId,
|
||||
query,
|
||||
callback
|
||||
) => callback(null, this.streams[fileId])
|
||||
|
||||
sinon.spy(this.FileStoreHandler, 'getFileStream')
|
||||
this.Features.hasFeature
|
||||
.withArgs('project-history-blobs')
|
||||
.returns(false)
|
||||
this.ProjectZipStreamManager.addAllFilesToArchive(
|
||||
this.project_id,
|
||||
this.archive,
|
||||
this.callback
|
||||
)
|
||||
for (const path in this.streams) {
|
||||
const stream = this.streams[path]
|
||||
stream.emit('end')
|
||||
}
|
||||
})
|
||||
|
||||
it('should get a stream for each file', function () {
|
||||
for (const path in this.files) {
|
||||
const file = this.files[path]
|
||||
|
||||
this.FileStoreHandler.getFileStream
|
||||
.calledWith(this.project_id, file._id)
|
||||
.should.equal(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ const MODULE_PATH = '../../../../app/src/Features/FileStore/FileStoreHandler.js'
|
||||
|
||||
describe('FileStoreHandler', function () {
|
||||
beforeEach(function () {
|
||||
this.fileSize = 999
|
||||
this.fs = {
|
||||
createReadStream: sinon.stub(),
|
||||
lstat: sinon.stub().callsArgWith(1, null, {
|
||||
@@ -40,7 +41,6 @@ describe('FileStoreHandler', function () {
|
||||
this.fileId = 'file_id_here'
|
||||
this.projectId = '1312312312'
|
||||
this.historyId = 123
|
||||
this.fileSize = 999
|
||||
this.hashValue = '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'
|
||||
this.fsPath = 'uploads/myfile.eps'
|
||||
this.getFileUrl = (projectId, fileId) =>
|
||||
@@ -69,6 +69,10 @@ describe('FileStoreHandler', function () {
|
||||
}),
|
||||
}
|
||||
|
||||
this.Features = {
|
||||
hasFeature: sinon.stub(),
|
||||
}
|
||||
|
||||
this.handler = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'@overleaf/settings': this.settings,
|
||||
@@ -76,6 +80,7 @@ describe('FileStoreHandler', function () {
|
||||
'../History/HistoryManager': this.HistoryManager,
|
||||
'../Project/ProjectDetailsHandler': this.ProjectDetailsHandler,
|
||||
'./FileHashManager': this.FileHashManager,
|
||||
'../../infrastructure/Features': this.Features,
|
||||
// FIXME: need to stub File object here
|
||||
'../../models/File': {
|
||||
File: this.FileModel,
|
||||
@@ -134,31 +139,55 @@ describe('FileStoreHandler', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should upload the file to the history store as a blob', function (done) {
|
||||
this.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
describe('when project-history-blobs feature is enabled', function () {
|
||||
it('should upload the file to the history store as a blob', function (done) {
|
||||
this.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
})
|
||||
this.Features.hasFeature.withArgs('project-history-blobs').returns(true)
|
||||
this.handler.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath,
|
||||
() => {
|
||||
this.HistoryManager.uploadBlobFromDisk
|
||||
.calledWith(
|
||||
this.historyId,
|
||||
this.hashValue,
|
||||
this.fileSize,
|
||||
this.fsPath
|
||||
)
|
||||
.should.equal(true)
|
||||
done()
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('when project-history-blobs feature is disabled', function () {
|
||||
it('should not upload the file to the history store as a blob', function (done) {
|
||||
this.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
})
|
||||
this.handler.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath,
|
||||
() => {
|
||||
this.HistoryManager.uploadBlobFromDisk.called.should.equal(false)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
this.handler.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath,
|
||||
() => {
|
||||
this.HistoryManager.uploadBlobFromDisk
|
||||
.calledWith(
|
||||
this.historyId,
|
||||
this.hashValue,
|
||||
this.fileSize,
|
||||
this.fsPath
|
||||
)
|
||||
.should.equal(true)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should create read stream', function (done) {
|
||||
|
||||
@@ -221,6 +221,9 @@ describe('ProjectDuplicator', function () {
|
||||
flushProjectToTpds: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.Features = {
|
||||
hasFeature: sinon.stub().withArgs('project-history-blobs').returns(true),
|
||||
}
|
||||
|
||||
this.ProjectDuplicator = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
@@ -241,6 +244,7 @@ describe('ProjectDuplicator', function () {
|
||||
'../ThirdPartyDataStore/TpdsProjectFlusher': this.TpdsProjectFlusher,
|
||||
'../Tags/TagsHandler': this.TagsHandler,
|
||||
'../History/HistoryManager': this.HistoryManager,
|
||||
'../../infrastructure/Features': this.Features,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,6 +54,7 @@ describe('ReferencesHandler', function () {
|
||||
filestore: { url: 'http://some.url/filestore' },
|
||||
project_history: { url: 'http://project-history.local' },
|
||||
},
|
||||
enableProjectHistoryBlobs: true,
|
||||
}),
|
||||
request: (this.request = {
|
||||
get: sinon.stub(),
|
||||
|
||||
@@ -57,6 +57,7 @@ describe('TpdsUpdateSender', function () {
|
||||
url: projectHistoryUrl,
|
||||
},
|
||||
},
|
||||
enableProjectHistoryBlobs: true,
|
||||
}
|
||||
const getUsers = sinon.stub()
|
||||
getUsers
|
||||
|
||||
Reference in New Issue
Block a user