mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-05 15:19:02 +02:00
[filestore] remove user files endpoints (#28125)
* [filestore] remove user files endpoints * [web] remove user files integration for filestore GitOrigin-RevId: 565fa68a659c07420ee6141d0f276b4e4d2972e0
This commit is contained in:
@@ -149,9 +149,6 @@ describe('ClsiManager', function () {
|
||||
this.ClsiCacheHandler = {
|
||||
clearCache: sinon.stub().resolves(),
|
||||
}
|
||||
this.Features = {
|
||||
hasFeature: sinon.stub().withArgs('project-history-blobs').returns(true),
|
||||
}
|
||||
this.HistoryManager = {
|
||||
getFilestoreBlobURL: sinon.stub().callsFake((historyId, hash) => {
|
||||
if (hash === GLOBAL_BLOB_HASH) {
|
||||
@@ -167,7 +164,6 @@ describe('ClsiManager', function () {
|
||||
'../../models/Project': {
|
||||
Project: this.Project,
|
||||
},
|
||||
'../../infrastructure/Features': this.Features,
|
||||
'../Project/ProjectEntityHandler': this.ProjectEntityHandler,
|
||||
'../Project/ProjectGetter': this.ProjectGetter,
|
||||
'../DocumentUpdater/DocumentUpdaterHandler':
|
||||
@@ -1048,20 +1044,15 @@ function _makeResources(project, docs, files) {
|
||||
})
|
||||
}
|
||||
for (const [path, file] of Object.entries(files)) {
|
||||
let url, fallbackURL
|
||||
let url
|
||||
if (file.hash === GLOBAL_BLOB_HASH) {
|
||||
url = `${FILESTORE_URL}/history/global/hash/${file.hash}`
|
||||
fallbackURL = `${FILESTORE_URL}/project/${project._id}/file/${file._id}`
|
||||
} else if (file.hash) {
|
||||
url = `${FILESTORE_URL}/history/project/${project.overleaf.history.id}/hash/${file.hash}`
|
||||
fallbackURL = `${FILESTORE_URL}/project/${project._id}/file/${file._id}`
|
||||
} else {
|
||||
url = `${FILESTORE_URL}/project/${project._id}/file/${file._id}`
|
||||
url = `${FILESTORE_URL}/history/project/${project.overleaf.history.id}/hash/${file.hash}`
|
||||
}
|
||||
resources.push({
|
||||
path: path.replace(/^\//, ''),
|
||||
url,
|
||||
fallbackURL,
|
||||
modified: file.created.getTime(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ describe('DocumentUpdaterHandler', function () {
|
||||
url: 'http://project_history.example.com',
|
||||
},
|
||||
},
|
||||
filestoreMigrationLevel: 0,
|
||||
moduleImportSequence: [],
|
||||
}
|
||||
this.source = 'dropbox'
|
||||
@@ -55,11 +54,6 @@ describe('DocumentUpdaterHandler', function () {
|
||||
done() {}
|
||||
},
|
||||
},
|
||||
'../FileStore/FileStoreHandler': {
|
||||
_buildUrl: sinon.stub().callsFake((projectId, fileId) => {
|
||||
return `http://filestore/project/${projectId}/file/${fileId}`
|
||||
}),
|
||||
},
|
||||
'../../infrastructure/Modules': {
|
||||
promises: {
|
||||
hooks: {
|
||||
@@ -1157,11 +1151,10 @@ describe('DocumentUpdaterHandler', function () {
|
||||
pathname: '/foo',
|
||||
docLines: 'a\nb',
|
||||
historyRangesSupport: false,
|
||||
url: undefined,
|
||||
hash: undefined,
|
||||
ranges: undefined,
|
||||
metadata: undefined,
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1195,7 +1188,6 @@ describe('DocumentUpdaterHandler', function () {
|
||||
newFiles: [
|
||||
{
|
||||
path: '/bar',
|
||||
url: 'filestore.example.com/file',
|
||||
file: { _id: this.fileId, hash: '12345' },
|
||||
},
|
||||
],
|
||||
@@ -1207,12 +1199,11 @@ describe('DocumentUpdaterHandler', function () {
|
||||
type: 'add-file',
|
||||
id: this.fileId.toString(),
|
||||
pathname: '/bar',
|
||||
url: 'filestore.example.com/file',
|
||||
docLines: undefined,
|
||||
historyRangesSupport: false,
|
||||
hash: '12345',
|
||||
ranges: undefined,
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
metadata: undefined,
|
||||
},
|
||||
]
|
||||
@@ -1290,7 +1281,6 @@ describe('DocumentUpdaterHandler', function () {
|
||||
oldFiles: [
|
||||
{
|
||||
path: '/foo.doc',
|
||||
url: 'filestore.example.com/file',
|
||||
file: { _id: this.fileId },
|
||||
},
|
||||
],
|
||||
@@ -1317,11 +1307,10 @@ describe('DocumentUpdaterHandler', function () {
|
||||
pathname: '/foo.doc',
|
||||
docLines: 'hello there',
|
||||
historyRangesSupport: false,
|
||||
url: undefined,
|
||||
hash: undefined,
|
||||
ranges: undefined,
|
||||
metadata: undefined,
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1421,11 +1410,10 @@ describe('DocumentUpdaterHandler', function () {
|
||||
pathname: '/foo',
|
||||
docLines: 'foo\nbar',
|
||||
historyRangesSupport: false,
|
||||
url: undefined,
|
||||
hash: undefined,
|
||||
ranges: this.ranges,
|
||||
metadata: undefined,
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1466,11 +1454,10 @@ describe('DocumentUpdaterHandler', function () {
|
||||
pathname: '/foo',
|
||||
docLines: 'foo\nbar',
|
||||
historyRangesSupport: true,
|
||||
url: undefined,
|
||||
hash: undefined,
|
||||
ranges: this.ranges,
|
||||
metadata: undefined,
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1498,16 +1485,12 @@ describe('DocumentUpdaterHandler', function () {
|
||||
})
|
||||
|
||||
describe('with filestore disabled', function () {
|
||||
beforeEach(function () {
|
||||
this.settings.filestoreMigrationLevel = 2
|
||||
})
|
||||
it('should add files without URL and with createdBlob', async function () {
|
||||
this.fileId = new ObjectId()
|
||||
this.changes = {
|
||||
newFiles: [
|
||||
{
|
||||
path: '/bar',
|
||||
url: 'filestore.example.com/file',
|
||||
file: { _id: this.fileId, hash: '12345' },
|
||||
},
|
||||
],
|
||||
@@ -1521,7 +1504,6 @@ describe('DocumentUpdaterHandler', function () {
|
||||
pathname: '/bar',
|
||||
docLines: undefined,
|
||||
historyRangesSupport: false,
|
||||
url: undefined,
|
||||
hash: '12345',
|
||||
ranges: undefined,
|
||||
createdBlob: true,
|
||||
@@ -1556,7 +1538,6 @@ describe('DocumentUpdaterHandler', function () {
|
||||
newFiles: [
|
||||
{
|
||||
path: '/bar',
|
||||
url: 'filestore.example.com/file',
|
||||
file: { _id: this.fileId },
|
||||
},
|
||||
],
|
||||
@@ -1671,16 +1652,14 @@ describe('DocumentUpdaterHandler', function () {
|
||||
file: fileId1,
|
||||
_hash: '42',
|
||||
path: '1.png',
|
||||
url: `http://filestore/project/${projectId}/file/${fileId1}`,
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
metadata: undefined,
|
||||
},
|
||||
{
|
||||
file: fileId2,
|
||||
_hash: '1337',
|
||||
path: '1.bib',
|
||||
url: `http://filestore/project/${projectId}/file/${fileId2}`,
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
metadata: {
|
||||
importedAt: fileCreated2,
|
||||
provider: 'references-provider',
|
||||
@@ -1690,8 +1669,7 @@ describe('DocumentUpdaterHandler', function () {
|
||||
file: fileId3,
|
||||
_hash: '21',
|
||||
path: 'bar.txt',
|
||||
url: `http://filestore/project/${projectId}/file/${fileId3}`,
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
metadata: {
|
||||
importedAt: fileCreated3,
|
||||
provider: 'project_output_file',
|
||||
@@ -1706,151 +1684,143 @@ describe('DocumentUpdaterHandler', function () {
|
||||
timeout: 6 * 60 * 1000,
|
||||
})
|
||||
})
|
||||
describe('with filestore disabled', function () {
|
||||
beforeEach(function () {
|
||||
this.settings.filestoreMigrationLevel = 2
|
||||
})
|
||||
it('should add files without URL', async function () {
|
||||
const fileId1 = new ObjectId()
|
||||
const fileId2 = new ObjectId()
|
||||
const fileId3 = new ObjectId()
|
||||
const fileCreated2 = new Date()
|
||||
const fileCreated3 = new Date()
|
||||
const otherProjectId = new ObjectId().toString()
|
||||
const files = [
|
||||
{ file: { _id: fileId1, hash: '42' }, path: '1.png' },
|
||||
{
|
||||
file: {
|
||||
_id: fileId2,
|
||||
hash: '1337',
|
||||
created: fileCreated2,
|
||||
linkedFileData: {
|
||||
it('should add files without URL', async function () {
|
||||
const fileId1 = new ObjectId()
|
||||
const fileId2 = new ObjectId()
|
||||
const fileId3 = new ObjectId()
|
||||
const fileCreated2 = new Date()
|
||||
const fileCreated3 = new Date()
|
||||
const otherProjectId = new ObjectId().toString()
|
||||
const files = [
|
||||
{ file: { _id: fileId1, hash: '42' }, path: '1.png' },
|
||||
{
|
||||
file: {
|
||||
_id: fileId2,
|
||||
hash: '1337',
|
||||
created: fileCreated2,
|
||||
linkedFileData: {
|
||||
provider: 'references-provider',
|
||||
},
|
||||
},
|
||||
path: '1.bib',
|
||||
},
|
||||
{
|
||||
file: {
|
||||
_id: fileId3,
|
||||
hash: '21',
|
||||
created: fileCreated3,
|
||||
linkedFileData: {
|
||||
provider: 'project_output_file',
|
||||
build_id: '1234-abc',
|
||||
clsiServerId: 'server-1',
|
||||
source_project_id: otherProjectId,
|
||||
source_output_file_path: 'foo/bar.txt',
|
||||
},
|
||||
},
|
||||
path: 'bar.txt',
|
||||
},
|
||||
]
|
||||
const docs = []
|
||||
this.request.yields(null, { statusCode: 200 })
|
||||
const projectId = new ObjectId()
|
||||
const projectHistoryId = 99
|
||||
await this.handler.promises.resyncProjectHistory(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
docs,
|
||||
files,
|
||||
{}
|
||||
)
|
||||
this.request.should.have.been.calledWith({
|
||||
url: `${this.settings.apis.documentupdater.url}/project/${projectId}/history/resync`,
|
||||
method: 'POST',
|
||||
json: {
|
||||
docs: [],
|
||||
files: [
|
||||
{
|
||||
file: fileId1,
|
||||
_hash: '42',
|
||||
path: '1.png',
|
||||
createdBlob: true,
|
||||
metadata: undefined,
|
||||
},
|
||||
{
|
||||
file: fileId2,
|
||||
_hash: '1337',
|
||||
path: '1.bib',
|
||||
createdBlob: true,
|
||||
metadata: {
|
||||
importedAt: fileCreated2,
|
||||
provider: 'references-provider',
|
||||
},
|
||||
},
|
||||
path: '1.bib',
|
||||
},
|
||||
{
|
||||
file: {
|
||||
_id: fileId3,
|
||||
hash: '21',
|
||||
created: fileCreated3,
|
||||
linkedFileData: {
|
||||
{
|
||||
file: fileId3,
|
||||
_hash: '21',
|
||||
path: 'bar.txt',
|
||||
createdBlob: true,
|
||||
metadata: {
|
||||
importedAt: fileCreated3,
|
||||
provider: 'project_output_file',
|
||||
build_id: '1234-abc',
|
||||
clsiServerId: 'server-1',
|
||||
source_project_id: otherProjectId,
|
||||
source_output_file_path: 'foo/bar.txt',
|
||||
// build_id and clsiServerId are omitted
|
||||
},
|
||||
},
|
||||
path: 'bar.txt',
|
||||
],
|
||||
projectHistoryId,
|
||||
},
|
||||
timeout: 6 * 60 * 1000,
|
||||
})
|
||||
})
|
||||
it('should flag files with missing hashes', async function () {
|
||||
const fileId1 = new ObjectId()
|
||||
const fileId2 = new ObjectId()
|
||||
const fileId3 = new ObjectId()
|
||||
const fileCreated2 = new Date()
|
||||
const fileCreated3 = new Date()
|
||||
const otherProjectId = new ObjectId().toString()
|
||||
const files = [
|
||||
{ file: { _id: fileId1, hash: '42' }, path: '1.png' },
|
||||
{
|
||||
file: {
|
||||
_id: fileId2,
|
||||
created: fileCreated2,
|
||||
linkedFileData: {
|
||||
provider: 'references-provider',
|
||||
},
|
||||
},
|
||||
]
|
||||
const docs = []
|
||||
this.request.yields(null, { statusCode: 200 })
|
||||
const projectId = new ObjectId()
|
||||
const projectHistoryId = 99
|
||||
await this.handler.promises.resyncProjectHistory(
|
||||
path: '1.bib',
|
||||
},
|
||||
{
|
||||
file: {
|
||||
_id: fileId3,
|
||||
hash: '21',
|
||||
created: fileCreated3,
|
||||
linkedFileData: {
|
||||
provider: 'project_output_file',
|
||||
build_id: '1234-abc',
|
||||
clsiServerId: 'server-1',
|
||||
source_project_id: otherProjectId,
|
||||
source_output_file_path: 'foo/bar.txt',
|
||||
},
|
||||
},
|
||||
path: 'bar.txt',
|
||||
},
|
||||
]
|
||||
const docs = []
|
||||
this.request.yields(null, { statusCode: 200 })
|
||||
const projectId = new ObjectId()
|
||||
const projectHistoryId = 99
|
||||
await expect(
|
||||
this.handler.promises.resyncProjectHistory(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
docs,
|
||||
files,
|
||||
{}
|
||||
)
|
||||
this.request.should.have.been.calledWith({
|
||||
url: `${this.settings.apis.documentupdater.url}/project/${projectId}/history/resync`,
|
||||
method: 'POST',
|
||||
json: {
|
||||
docs: [],
|
||||
files: [
|
||||
{
|
||||
file: fileId1,
|
||||
_hash: '42',
|
||||
path: '1.png',
|
||||
url: undefined,
|
||||
createdBlob: true,
|
||||
metadata: undefined,
|
||||
},
|
||||
{
|
||||
file: fileId2,
|
||||
_hash: '1337',
|
||||
path: '1.bib',
|
||||
url: undefined,
|
||||
createdBlob: true,
|
||||
metadata: {
|
||||
importedAt: fileCreated2,
|
||||
provider: 'references-provider',
|
||||
},
|
||||
},
|
||||
{
|
||||
file: fileId3,
|
||||
_hash: '21',
|
||||
path: 'bar.txt',
|
||||
url: undefined,
|
||||
createdBlob: true,
|
||||
metadata: {
|
||||
importedAt: fileCreated3,
|
||||
provider: 'project_output_file',
|
||||
source_project_id: otherProjectId,
|
||||
source_output_file_path: 'foo/bar.txt',
|
||||
// build_id and clsiServerId are omitted
|
||||
},
|
||||
},
|
||||
],
|
||||
projectHistoryId,
|
||||
},
|
||||
timeout: 6 * 60 * 1000,
|
||||
})
|
||||
})
|
||||
it('should flag files with missing hashes', async function () {
|
||||
const fileId1 = new ObjectId()
|
||||
const fileId2 = new ObjectId()
|
||||
const fileId3 = new ObjectId()
|
||||
const fileCreated2 = new Date()
|
||||
const fileCreated3 = new Date()
|
||||
const otherProjectId = new ObjectId().toString()
|
||||
const files = [
|
||||
{ file: { _id: fileId1, hash: '42' }, path: '1.png' },
|
||||
{
|
||||
file: {
|
||||
_id: fileId2,
|
||||
created: fileCreated2,
|
||||
linkedFileData: {
|
||||
provider: 'references-provider',
|
||||
},
|
||||
},
|
||||
path: '1.bib',
|
||||
},
|
||||
{
|
||||
file: {
|
||||
_id: fileId3,
|
||||
hash: '21',
|
||||
created: fileCreated3,
|
||||
linkedFileData: {
|
||||
provider: 'project_output_file',
|
||||
build_id: '1234-abc',
|
||||
clsiServerId: 'server-1',
|
||||
source_project_id: otherProjectId,
|
||||
source_output_file_path: 'foo/bar.txt',
|
||||
},
|
||||
},
|
||||
path: 'bar.txt',
|
||||
},
|
||||
]
|
||||
const docs = []
|
||||
this.request.yields(null, { statusCode: 200 })
|
||||
const projectId = new ObjectId()
|
||||
const projectHistoryId = 99
|
||||
await expect(
|
||||
this.handler.promises.resyncProjectHistory(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
docs,
|
||||
files,
|
||||
{}
|
||||
)
|
||||
).to.be.rejected
|
||||
})
|
||||
).to.be.rejected
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -58,15 +58,6 @@ describe('ProjectZipStreamManager', function () {
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/Features', () => ({
|
||||
default: (ctx.Features = {
|
||||
hasFeature: sinon
|
||||
.stub()
|
||||
.withArgs('project-history-blobs')
|
||||
.returns(true),
|
||||
}),
|
||||
}))
|
||||
|
||||
ctx.ProjectZipStreamManager = (await import(modulePath)).default
|
||||
})
|
||||
|
||||
@@ -411,93 +402,55 @@ describe('ProjectZipStreamManager', function () {
|
||||
},
|
||||
}
|
||||
ctx.streams = {
|
||||
'file-id-1': new EventEmitter(),
|
||||
'file-id-2': new EventEmitter(),
|
||||
abc: new EventEmitter(),
|
||||
def: new EventEmitter(),
|
||||
}
|
||||
ctx.ProjectEntityHandler.getAllFiles = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.files)
|
||||
ctx.HistoryManager.requestBlobWithProjectId = (
|
||||
projectId,
|
||||
hash,
|
||||
callback
|
||||
) => {
|
||||
return callback(null, { stream: ctx.streams[hash] })
|
||||
}
|
||||
sinon.spy(ctx.HistoryManager, 'requestBlobWithProjectId')
|
||||
ctx.ProjectZipStreamManager.addAllFilesToArchive(
|
||||
ctx.project_id,
|
||||
ctx.archive,
|
||||
ctx.callback
|
||||
)
|
||||
for (const hash in ctx.streams) {
|
||||
const stream = ctx.streams[hash]
|
||||
stream.emit('end')
|
||||
}
|
||||
})
|
||||
describe('with project-history-blobs feature enabled', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.HistoryManager.requestBlobWithFallback = (
|
||||
projectId,
|
||||
hash,
|
||||
fileId,
|
||||
callback
|
||||
) => {
|
||||
return callback(null, { stream: ctx.streams[fileId] })
|
||||
}
|
||||
sinon.spy(ctx.HistoryManager, 'requestBlobWithFallback')
|
||||
ctx.ProjectZipStreamManager.addAllFilesToArchive(
|
||||
ctx.project_id,
|
||||
ctx.archive,
|
||||
ctx.callback
|
||||
)
|
||||
for (const path in ctx.streams) {
|
||||
const stream = ctx.streams[path]
|
||||
stream.emit('end')
|
||||
}
|
||||
})
|
||||
|
||||
it('should get the files for the project', function (ctx) {
|
||||
return ctx.ProjectEntityHandler.getAllFiles
|
||||
.calledWith(ctx.project_id)
|
||||
it('should get the files for the project', function (ctx) {
|
||||
return ctx.ProjectEntityHandler.getAllFiles
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should get a stream for each file', function (ctx) {
|
||||
for (const path in ctx.files) {
|
||||
const file = ctx.files[path]
|
||||
|
||||
ctx.HistoryManager.requestBlobWithProjectId
|
||||
.calledWith(ctx.project_id, file.hash)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should get a stream for each file', function (ctx) {
|
||||
for (const path in ctx.files) {
|
||||
const file = ctx.files[path]
|
||||
|
||||
ctx.HistoryManager.requestBlobWithFallback
|
||||
.calledWith(ctx.project_id, file.hash, file._id)
|
||||
.should.equal(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('should add each file to the archive', function (ctx) {
|
||||
for (let path in ctx.files) {
|
||||
const file = ctx.files[path]
|
||||
path = path.slice(1) // remove "/"
|
||||
ctx.archive.append
|
||||
.calledWith(ctx.streams[file._id], { name: path })
|
||||
.should.equal(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('with project-history-blobs feature disabled', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.FileStoreHandler.getFileStream = (
|
||||
projectId,
|
||||
fileId,
|
||||
query,
|
||||
callback
|
||||
) => callback(null, ctx.streams[fileId])
|
||||
|
||||
sinon.spy(ctx.FileStoreHandler, 'getFileStream')
|
||||
ctx.Features.hasFeature.withArgs('project-history-blobs').returns(false)
|
||||
ctx.ProjectZipStreamManager.addAllFilesToArchive(
|
||||
ctx.project_id,
|
||||
ctx.archive,
|
||||
ctx.callback
|
||||
)
|
||||
for (const path in ctx.streams) {
|
||||
const stream = ctx.streams[path]
|
||||
stream.emit('end')
|
||||
}
|
||||
})
|
||||
|
||||
it('should get a stream for each file', function (ctx) {
|
||||
for (const path in ctx.files) {
|
||||
const file = ctx.files[path]
|
||||
|
||||
ctx.FileStoreHandler.getFileStream
|
||||
.calledWith(ctx.project_id, file._id)
|
||||
.should.equal(true)
|
||||
}
|
||||
})
|
||||
it('should add each file to the archive', function (ctx) {
|
||||
for (let path in ctx.files) {
|
||||
const file = ctx.files[path]
|
||||
path = path.slice(1) // remove "/"
|
||||
ctx.archive.append.should.have.been.calledWith(ctx.streams[file.hash], {
|
||||
name: path,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,7 +8,6 @@ const MODULE_PATH =
|
||||
|
||||
const expectedFileHeaders = {
|
||||
'Cache-Control': 'private, max-age=3600',
|
||||
'X-Served-By': 'filestore',
|
||||
}
|
||||
|
||||
vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
|
||||
@@ -17,15 +16,11 @@ vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
|
||||
|
||||
describe('FileStoreController', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.FileStoreHandler = {
|
||||
promises: {
|
||||
getFileStream: sinon.stub(),
|
||||
getFileSize: sinon.stub(),
|
||||
},
|
||||
}
|
||||
ctx.ProjectLocator = { promises: { findElement: sinon.stub() } }
|
||||
ctx.Stream = { pipeline: sinon.stub().resolves() }
|
||||
ctx.HistoryManager = {}
|
||||
ctx.HistoryManager = {
|
||||
promises: { requestBlobWithProjectId: sinon.stub() },
|
||||
}
|
||||
|
||||
vi.doMock('node:stream/promises', () => ctx.Stream)
|
||||
|
||||
@@ -37,13 +32,6 @@ describe('FileStoreController', function () {
|
||||
default: ctx.ProjectLocator,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/FileStore/FileStoreHandler',
|
||||
() => ({
|
||||
default: ctx.FileStoreHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/History/HistoryManager', () => ({
|
||||
default: ctx.HistoryManager,
|
||||
}))
|
||||
@@ -67,21 +55,24 @@ describe('FileStoreController', function () {
|
||||
}
|
||||
ctx.res = new MockResponse()
|
||||
ctx.next = sinon.stub()
|
||||
ctx.file = { name: 'myfile.png' }
|
||||
ctx.hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
ctx.file = { name: 'myfile.png', hash: ctx.hash }
|
||||
})
|
||||
|
||||
describe('getFile', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.FileStoreHandler.promises.getFileStream.resolves(ctx.stream)
|
||||
ctx.HistoryManager.promises.requestBlobWithProjectId.resolves({
|
||||
stream: ctx.stream,
|
||||
})
|
||||
ctx.ProjectLocator.promises.findElement.resolves({ element: ctx.file })
|
||||
})
|
||||
|
||||
it('should call the file store handler with the project_id file_id and any query string', async function (ctx) {
|
||||
it('should call the history manager with the project_id hash', async function (ctx) {
|
||||
await ctx.controller.getFile(ctx.req, ctx.res)
|
||||
ctx.FileStoreHandler.promises.getFileStream.should.have.been.calledWith(
|
||||
ctx.HistoryManager.promises.requestBlobWithProjectId.should.have.been.calledWith(
|
||||
ctx.req.params.Project_id,
|
||||
ctx.req.params.File_id,
|
||||
ctx.req.query
|
||||
ctx.hash,
|
||||
'GET'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -217,12 +208,12 @@ describe('FileStoreController', function () {
|
||||
it('reports the file size', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
const expectedFileSize = 99393
|
||||
ctx.FileStoreHandler.promises.getFileSize.rejects(
|
||||
ctx.HistoryManager.promises.requestBlobWithProjectId.rejects(
|
||||
new Error('getFileSize: unexpected arguments')
|
||||
)
|
||||
ctx.FileStoreHandler.promises.getFileSize
|
||||
.withArgs(ctx.projectId, ctx.fileId)
|
||||
.resolves(expectedFileSize)
|
||||
ctx.HistoryManager.promises.requestBlobWithProjectId
|
||||
.withArgs(ctx.projectId, ctx.hash)
|
||||
.resolves({ contentLength: expectedFileSize })
|
||||
|
||||
ctx.res.end = () => {
|
||||
expect(ctx.res.status.lastCall.args).to.deep.equal([200])
|
||||
@@ -239,7 +230,7 @@ describe('FileStoreController', function () {
|
||||
|
||||
it('returns 404 on NotFoundError', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.FileStoreHandler.promises.getFileSize.rejects(
|
||||
ctx.HistoryManager.promises.requestBlobWithProjectId.rejects(
|
||||
new Errors.NotFoundError()
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
const { assert, expect } = require('chai')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const Errors = require('../../../../app/src/Features/Errors/Errors')
|
||||
const OError = require('@overleaf/o-error')
|
||||
|
||||
const MODULE_PATH = '../../../../app/src/Features/FileStore/FileStoreHandler.js'
|
||||
|
||||
@@ -133,206 +131,52 @@ describe('FileStoreHandler', function () {
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
describe('when project-history-blobs feature is enabled', function () {
|
||||
it('should upload the file to the history store as a blob', async function () {
|
||||
this.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
})
|
||||
this.Features.hasFeature.withArgs('project-history-blobs').returns(true)
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
this.HistoryManager.uploadBlobFromDisk
|
||||
.calledWith(
|
||||
this.historyId,
|
||||
this.hashValue,
|
||||
this.fileSize,
|
||||
this.fsPath
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
describe('when project-history-blobs feature is disabled', function () {
|
||||
it('should not upload the file to the history store as a blob', async function () {
|
||||
this.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
})
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
this.HistoryManager.uploadBlobFromDisk.called.should.equal(false)
|
||||
it('should upload the file to the history store as a blob', async function () {
|
||||
this.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
})
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
this.HistoryManager.uploadBlobFromDisk
|
||||
.calledWith(this.historyId, this.hashValue, this.fileSize, this.fsPath)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
describe('when filestore feature is enabled', function () {
|
||||
beforeEach(function () {
|
||||
this.Features.hasFeature.withArgs('filestore').returns(true)
|
||||
})
|
||||
it('should create read stream', function (done) {
|
||||
this.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
})
|
||||
this.handler.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath,
|
||||
() => {
|
||||
this.fs.createReadStream.calledWith(this.fsPath).should.equal(true)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should pipe the read stream to request', function () {
|
||||
this.request.returns(this.writeStream)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.fs.createReadStream.returns({
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
pipe: o => {
|
||||
this.writeStream.should.equal(o)
|
||||
resolve()
|
||||
},
|
||||
})
|
||||
this.handler.promises
|
||||
.uploadFileFromDisk(this.projectId, this.fileArgs, this.fsPath)
|
||||
.catch(reject)
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass the correct options to request', async function () {
|
||||
const fileUrl = this.getFileUrl(this.projectId, this.fileId)
|
||||
this.fs.createReadStream.returns({
|
||||
pipe: sinon.stub(),
|
||||
on: sinon.stub((type, cb) => {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
}),
|
||||
})
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
this.request.args[0][0].method.should.equal('post')
|
||||
this.request.args[0][0].uri.should.equal(fileUrl)
|
||||
})
|
||||
|
||||
it('should resolve with the url and fileRef', async function () {
|
||||
const fileUrl = this.getFileUrl(this.projectId, this.fileId)
|
||||
this.fs.createReadStream.returns({
|
||||
pipe: sinon.stub(),
|
||||
on: sinon.stub((type, cb) => {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
}),
|
||||
})
|
||||
const { url, fileRef } = await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
expect(url).to.equal(fileUrl)
|
||||
expect(fileRef._id).to.equal(this.fileId)
|
||||
expect(fileRef.hash).to.equal(this.hashValue)
|
||||
})
|
||||
describe('when upload to filestore fails', function () {
|
||||
beforeEach(function () {
|
||||
this.writeStream.on = function (type, fn) {
|
||||
if (type === 'response') {
|
||||
fn({ statusCode: 500 })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should reject with an error', async function () {
|
||||
this.fs.createReadStream.callCount = 0
|
||||
this.fs.createReadStream.returns({
|
||||
pipe: sinon.stub(),
|
||||
on: sinon.stub((type, cb) => {
|
||||
if (type === 'open') {
|
||||
cb()
|
||||
}
|
||||
}),
|
||||
})
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.be.instanceOf(Error)
|
||||
|
||||
expect(this.fs.createReadStream.callCount).to.equal(
|
||||
this.handler.RETRY_ATTEMPTS
|
||||
)
|
||||
})
|
||||
})
|
||||
it('should not open file handle', async function () {
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
expect(this.fs.createReadStream).to.not.have.been.called
|
||||
})
|
||||
describe('when filestore feature is disabled', function () {
|
||||
beforeEach(function () {
|
||||
this.Features.hasFeature.withArgs('filestore').returns(false)
|
||||
})
|
||||
it('should not open file handle', async function () {
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
expect(this.fs.createReadStream).to.not.have.been.called
|
||||
})
|
||||
|
||||
it('should not talk to filestore', async function () {
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
it('should not talk to filestore', async function () {
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
|
||||
expect(this.request).to.not.have.been.called
|
||||
})
|
||||
expect(this.request).to.not.have.been.called
|
||||
})
|
||||
|
||||
it('should resolve with the url and fileRef', async function () {
|
||||
const fileUrl = this.getFileUrl(this.projectId, this.fileId)
|
||||
const { url, fileRef } = await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
expect(url).to.equal(fileUrl)
|
||||
expect(fileRef._id).to.equal(this.fileId)
|
||||
expect(fileRef.hash).to.equal(this.hashValue)
|
||||
})
|
||||
it('should resolve with the url and fileRef', async function () {
|
||||
const { fileRef } = await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
)
|
||||
expect(fileRef._id).to.equal(this.fileId)
|
||||
expect(fileRef.hash).to.equal(this.hashValue)
|
||||
})
|
||||
|
||||
describe('symlink', function () {
|
||||
@@ -383,341 +227,4 @@ describe('FileStoreHandler', function () {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteFile', function () {
|
||||
it('should send a delete request to filestore api', function (done) {
|
||||
const fileUrl = this.getFileUrl(this.projectId, this.fileId)
|
||||
this.request.callsArgWith(1, null)
|
||||
|
||||
this.handler.deleteFile(this.projectId, this.fileId, err => {
|
||||
assert.equal(err, undefined)
|
||||
this.request.args[0][0].method.should.equal('delete')
|
||||
this.request.args[0][0].uri.should.equal(fileUrl)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should reject with the error if there is one', async function () {
|
||||
const expectedError = 'my error'
|
||||
this.request.callsArgWith(1, expectedError)
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.deleteFile(this.projectId, this.fileId)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.equal(expectedError)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteProject', function () {
|
||||
describe('when filestore is enabled', function () {
|
||||
beforeEach(function () {
|
||||
this.Features.hasFeature.withArgs('filestore').returns(true)
|
||||
})
|
||||
it('should send a delete request to filestore api', async function () {
|
||||
const projectUrl = this.getProjectUrl(this.projectId)
|
||||
this.request.callsArgWith(1, null)
|
||||
|
||||
await this.handler.promises.deleteProject(this.projectId)
|
||||
this.request.args[0][0].method.should.equal('delete')
|
||||
this.request.args[0][0].uri.should.equal(projectUrl)
|
||||
})
|
||||
|
||||
it('should wrap the error if there is one', async function () {
|
||||
const expectedError = new Error('my error')
|
||||
this.request.callsArgWith(1, expectedError)
|
||||
const promise = this.handler.promises.deleteProject(this.projectId)
|
||||
let error
|
||||
|
||||
try {
|
||||
await promise
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.exist
|
||||
|
||||
expect(OError.getFullStack(error)).to.match(
|
||||
/something went wrong deleting a project in filestore/
|
||||
)
|
||||
expect(OError.getFullStack(error)).to.match(/my error/)
|
||||
})
|
||||
})
|
||||
describe('when filestore is disabled', function () {
|
||||
beforeEach(function () {
|
||||
this.Features.hasFeature.withArgs('filestore').returns(false)
|
||||
})
|
||||
it('should not send a delete request to filestore api', async function () {
|
||||
await this.handler.promises.deleteProject(this.projectId)
|
||||
|
||||
this.request.called.should.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getFileStream', function () {
|
||||
describe('when filestore is disabled', function () {
|
||||
beforeEach(function () {
|
||||
this.Features.hasFeature.withArgs('filestore').returns(false)
|
||||
})
|
||||
|
||||
it('should callback with a NotFoundError', async function () {
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.getFileStream(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
{}
|
||||
)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.be.instanceOf(Errors.NotFoundError)
|
||||
})
|
||||
|
||||
it('should not call request', async function () {
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.getFileStream(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
{}
|
||||
)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.exist
|
||||
this.request.called.should.equal(false)
|
||||
})
|
||||
})
|
||||
describe('when filestore is enabled', function () {
|
||||
beforeEach(function () {
|
||||
this.query = {}
|
||||
this.request.returns(this.readStream)
|
||||
this.Features.hasFeature.withArgs('filestore').returns(true)
|
||||
})
|
||||
|
||||
it('should get the stream with the correct params', async function () {
|
||||
const fileUrl = this.getFileUrl(this.projectId, this.fileId)
|
||||
await this.handler.promises.getFileStream(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
this.query
|
||||
)
|
||||
this.request.args[0][0].method.should.equal('get')
|
||||
this.request.args[0][0].uri.should.equal(
|
||||
fileUrl + '?from=getFileStream'
|
||||
)
|
||||
})
|
||||
|
||||
it('should get stream from request', async function () {
|
||||
const stream = await this.handler.promises.getFileStream(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
this.query
|
||||
)
|
||||
|
||||
stream.should.equal(this.readStream)
|
||||
})
|
||||
|
||||
it('should add an error handler', async function () {
|
||||
const stream = await this.handler.promises.getFileStream(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
this.query
|
||||
)
|
||||
stream.on.calledWith('error').should.equal(true)
|
||||
})
|
||||
|
||||
describe('when range is specified in query', function () {
|
||||
beforeEach(function () {
|
||||
this.query = { range: '0-10' }
|
||||
})
|
||||
|
||||
it('should add a range header', async function () {
|
||||
await this.handler.promises.getFileStream(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
this.query
|
||||
)
|
||||
|
||||
this.request.callCount.should.equal(1)
|
||||
const { headers } = this.request.firstCall.args[0]
|
||||
expect(headers).to.have.keys('range')
|
||||
expect(headers.range).to.equal('bytes=0-10')
|
||||
})
|
||||
|
||||
describe('when range is invalid', function () {
|
||||
;['0-', '-100', 'one-two', 'nonsense'].forEach(r => {
|
||||
beforeEach(function () {
|
||||
this.query = { range: `${r}` }
|
||||
})
|
||||
|
||||
it(`should not add a range header for '${r}'`, async function () {
|
||||
await this.handler.promises.getFileStream(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
this.query
|
||||
)
|
||||
this.request.callCount.should.equal(1)
|
||||
const { headers } = this.request.firstCall.args[0]
|
||||
expect(headers).to.not.have.keys('range')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getFileSize', function () {
|
||||
it('returns the file size reported by filestore', async function () {
|
||||
const expectedFileSize = 32432
|
||||
const fileUrl =
|
||||
this.getFileUrl(this.projectId, this.fileId) + '?from=getFileSize'
|
||||
this.request.head.yields(
|
||||
new Error('request.head() received unexpected arguments')
|
||||
)
|
||||
this.request.head.withArgs(fileUrl).yields(null, {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'content-length': expectedFileSize,
|
||||
},
|
||||
})
|
||||
|
||||
const fileSize = await this.handler.promises.getFileSize(
|
||||
this.projectId,
|
||||
this.fileId
|
||||
)
|
||||
expect(fileSize).to.equal(expectedFileSize)
|
||||
})
|
||||
|
||||
it('throws a NotFoundError on a 404 from filestore', async function () {
|
||||
this.request.head.yields(null, { statusCode: 404 })
|
||||
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.getFileSize(this.projectId, this.fileId)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.be.instanceOf(Errors.NotFoundError)
|
||||
})
|
||||
|
||||
it('throws an error on a non-200 from filestore', async function () {
|
||||
this.request.head.yields(null, { statusCode: 500 })
|
||||
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.getFileSize(this.projectId, this.fileId)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.be.instanceOf(Error)
|
||||
})
|
||||
|
||||
it('rethrows errors from filestore', async function () {
|
||||
const expectedError = new Error('from filestore')
|
||||
this.request.head.yields(expectedError)
|
||||
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.getFileSize(this.projectId, this.fileId)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.equal(expectedError)
|
||||
})
|
||||
})
|
||||
|
||||
describe('copyFile', function () {
|
||||
beforeEach(function () {
|
||||
this.newProjectId = 'new project'
|
||||
this.newFileId = 'new file id'
|
||||
})
|
||||
|
||||
it('should post json', async function () {
|
||||
const newFileUrl = this.getFileUrl(this.newProjectId, this.newFileId)
|
||||
this.request.callsArgWith(1, null, { statusCode: 200 })
|
||||
|
||||
await this.handler.promises.copyFile(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
this.newProjectId,
|
||||
this.newFileId
|
||||
)
|
||||
this.request.args[0][0].method.should.equal('put')
|
||||
this.request.args[0][0].uri.should.equal(newFileUrl)
|
||||
this.request.args[0][0].json.source.project_id.should.equal(
|
||||
this.projectId
|
||||
)
|
||||
this.request.args[0][0].json.source.file_id.should.equal(this.fileId)
|
||||
})
|
||||
|
||||
it('returns the url', async function () {
|
||||
const expectedUrl = this.getFileUrl(this.newProjectId, this.newFileId)
|
||||
this.request.callsArgWith(1, null, { statusCode: 200 })
|
||||
const url = await this.handler.promises.copyFile(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
this.newProjectId,
|
||||
this.newFileId
|
||||
)
|
||||
|
||||
url.should.equal(expectedUrl)
|
||||
})
|
||||
|
||||
it('should return the err', async function () {
|
||||
const expectedError = new Error('error')
|
||||
this.request.callsArgWith(1, expectedError)
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.copyFile(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
this.newProjectId,
|
||||
this.newFileId
|
||||
)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.equal(expectedError)
|
||||
})
|
||||
|
||||
it('should return an error for a non-success statusCode', async function () {
|
||||
this.request.callsArgWith(1, null, { statusCode: 500 })
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.copyFile(
|
||||
this.projectId,
|
||||
this.fileId,
|
||||
this.newProjectId,
|
||||
this.newFileId
|
||||
)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
expect(error).to.be.instanceOf(Error)
|
||||
expect(error).to.have.property(
|
||||
'message',
|
||||
'non-ok response from filestore for copyFile: 500'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -115,11 +115,6 @@ describe('ProjectDeleter', function () {
|
||||
|
||||
this.ProjectMock = sinon.mock(Project)
|
||||
this.DeletedProjectMock = sinon.mock(DeletedProject)
|
||||
this.FileStoreHandler = {
|
||||
promises: {
|
||||
deleteProject: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.Features = {
|
||||
hasFeature: sinon.stub().returns(true),
|
||||
}
|
||||
@@ -143,7 +138,6 @@ describe('ProjectDeleter', function () {
|
||||
'../DocumentUpdater/DocumentUpdaterHandler':
|
||||
this.DocumentUpdaterHandler,
|
||||
'../Tags/TagsHandler': this.TagsHandler,
|
||||
'../FileStore/FileStoreHandler': this.FileStoreHandler,
|
||||
'../Chat/ChatApiHandler': this.ChatApiHandler,
|
||||
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
|
||||
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
|
||||
@@ -480,12 +474,6 @@ describe('ProjectDeleter', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should destroy the files in filestore', function () {
|
||||
expect(
|
||||
this.FileStoreHandler.promises.deleteProject
|
||||
).to.have.been.calledWith(this.deletedProjects[0].project._id)
|
||||
})
|
||||
|
||||
it('should destroy the chat threads and messages', function () {
|
||||
expect(
|
||||
this.ChatApiHandler.promises.destroyProject
|
||||
@@ -540,11 +528,6 @@ describe('ProjectDeleter', function () {
|
||||
.called
|
||||
})
|
||||
|
||||
it('should not destroy the files in filestore', function () {
|
||||
expect(this.FileStoreHandler.promises.deleteProject).to.not.have.been
|
||||
.called
|
||||
})
|
||||
|
||||
it('should not destroy the chat threads and messages', function () {
|
||||
expect(this.ChatApiHandler.promises.destroyProject).to.not.have.been
|
||||
.called
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('ProjectDuplicator', function () {
|
||||
this.doc1Lines = ['one']
|
||||
this.doc2Lines = ['two']
|
||||
this.file0 = { name: 'file0', _id: 'file0', hash: 'abcde' }
|
||||
this.file1 = { name: 'file1', _id: 'file1' }
|
||||
this.file1 = { name: 'file1', _id: 'file1', hash: 'fffff' }
|
||||
this.file2 = {
|
||||
name: 'file2',
|
||||
_id: 'file2',
|
||||
@@ -105,22 +105,19 @@ describe('ProjectDuplicator', function () {
|
||||
]
|
||||
this.fileEntries = [
|
||||
{
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
path: this.file0Path,
|
||||
file: this.newFile0,
|
||||
url: this.filestoreUrl,
|
||||
},
|
||||
{
|
||||
createdBlob: false,
|
||||
createdBlob: true,
|
||||
path: this.file1Path,
|
||||
file: this.newFile1,
|
||||
url: this.filestoreUrl,
|
||||
},
|
||||
{
|
||||
createdBlob: true,
|
||||
path: this.file2Path,
|
||||
file: this.newFile2,
|
||||
url: null,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -143,15 +140,10 @@ describe('ProjectDuplicator', function () {
|
||||
updateProjectStructure: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.FileStoreHandler = {
|
||||
promises: {
|
||||
copyFile: sinon.stub().resolves(this.filestoreUrl),
|
||||
},
|
||||
}
|
||||
this.HistoryManager = {
|
||||
promises: {
|
||||
copyBlob: sinon.stub().callsFake((historyId, newHistoryId, hash) => {
|
||||
if (hash === 'abcde') {
|
||||
if (hash === '500') {
|
||||
return Promise.reject(new Error('copy blob error'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
@@ -221,9 +213,6 @@ 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: {
|
||||
@@ -232,7 +221,6 @@ describe('ProjectDuplicator', function () {
|
||||
'../Docstore/DocstoreManager': this.DocstoreManager,
|
||||
'../DocumentUpdater/DocumentUpdaterHandler':
|
||||
this.DocumentUpdaterHandler,
|
||||
'../FileStore/FileStoreHandler': this.FileStoreHandler,
|
||||
'./ProjectCreationHandler': this.ProjectCreationHandler,
|
||||
'./ProjectDeleter': this.ProjectDeleter,
|
||||
'./ProjectEntityMongoUpdateHandler':
|
||||
@@ -244,7 +232,6 @@ describe('ProjectDuplicator', function () {
|
||||
'../ThirdPartyDataStore/TpdsProjectFlusher': this.TpdsProjectFlusher,
|
||||
'../Tags/TagsHandler': this.TagsHandler,
|
||||
'../History/HistoryManager': this.HistoryManager,
|
||||
'../../infrastructure/Features': this.Features,
|
||||
'../Compile/ClsiCacheManager': {
|
||||
prepareClsiCache: sinon.stub().rejects(new Error('ignore this')),
|
||||
},
|
||||
@@ -281,7 +268,7 @@ describe('ProjectDuplicator', function () {
|
||||
})
|
||||
|
||||
it('should duplicate the files with hashes by copying the blobs in history v1', function () {
|
||||
for (const file of [this.file0, this.file2]) {
|
||||
for (const file of [this.file0, this.file1, this.file2]) {
|
||||
this.HistoryManager.promises.copyBlob.should.have.been.calledWith(
|
||||
this.project.overleaf.history.id,
|
||||
this.newProject.overleaf.history.id,
|
||||
@@ -290,46 +277,6 @@ describe('ProjectDuplicator', function () {
|
||||
}
|
||||
})
|
||||
|
||||
it('should ignore any errors when copying the blobs in history v1', async function () {
|
||||
await expect(
|
||||
this.HistoryManager.promises.copyBlob(
|
||||
this.project.overleaf.history.id,
|
||||
this.newProject.overleaf.history.id,
|
||||
this.file0.hash
|
||||
)
|
||||
).to.be.rejectedWith('copy blob error')
|
||||
})
|
||||
|
||||
it('should not try to copy the blobs for any files without hashes', function () {
|
||||
for (const file of [this.file1]) {
|
||||
this.HistoryManager.promises.copyBlob.should.not.have.been.calledWith(
|
||||
this.project.overleaf.history.id,
|
||||
this.newProject.overleaf.history.id,
|
||||
file.hash
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should copy files to the filestore', function () {
|
||||
for (const file of [this.file0, this.file1]) {
|
||||
this.FileStoreHandler.promises.copyFile.should.have.been.calledWith(
|
||||
this.project._id,
|
||||
file._id,
|
||||
this.newProject._id,
|
||||
this.newFileId
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should not copy files that have been sent to history-v1 to the filestore', function () {
|
||||
this.FileStoreHandler.promises.copyFile.should.not.have.been.calledWith(
|
||||
this.project._id,
|
||||
this.file2._id,
|
||||
this.newProject._id,
|
||||
this.newFileId
|
||||
)
|
||||
})
|
||||
|
||||
it('should create a blank project', function () {
|
||||
this.ProjectCreationHandler.promises.createBlankProject.should.have.been.calledWith(
|
||||
this.owner._id,
|
||||
@@ -421,6 +368,19 @@ describe('ProjectDuplicator', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when cloning in history-v1 fails', function () {
|
||||
it('should fail the clone operation', async function () {
|
||||
this.file0.hash = '500'
|
||||
await expect(
|
||||
this.ProjectDuplicator.promises.duplicate(
|
||||
this.owner,
|
||||
this.project._id,
|
||||
'name'
|
||||
)
|
||||
).to.be.rejectedWith('copy blob error')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is an error', function () {
|
||||
beforeEach(async function () {
|
||||
this.ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure.rejects()
|
||||
|
||||
@@ -26,7 +26,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
},
|
||||
},
|
||||
}
|
||||
this.fileUrl = 'filestore.example.com/file'
|
||||
this.user = { _id: new ObjectId() }
|
||||
|
||||
this.DocModel = class Doc {
|
||||
@@ -152,16 +151,8 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
}
|
||||
this.FileStoreHandler = {
|
||||
promises: {
|
||||
copyFile: sinon.stub(),
|
||||
uploadFileFromDisk: sinon.stub(),
|
||||
deleteFile: sinon.stub(),
|
||||
},
|
||||
|
||||
_buildUrl: sinon
|
||||
.stub()
|
||||
.callsFake(
|
||||
(projectId, fileId) => `www.filestore.test/${projectId}/${fileId}`
|
||||
),
|
||||
}
|
||||
this.FileWriter = {
|
||||
promises: {
|
||||
@@ -670,7 +661,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
linkedFileData: this.linkedFileData,
|
||||
}
|
||||
this.FileStoreHandler.promises.uploadFileFromDisk.resolves({
|
||||
url: this.fileUrl,
|
||||
fileRef: this.newFile,
|
||||
createdBlob: true,
|
||||
})
|
||||
@@ -730,7 +720,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
{
|
||||
file: this.newFile,
|
||||
path: this.path,
|
||||
url: this.fileUrl,
|
||||
createdBlob: true,
|
||||
},
|
||||
]
|
||||
@@ -1069,7 +1058,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
describe('upsertFile', function () {
|
||||
beforeEach(function () {
|
||||
this.FileStoreHandler.promises.uploadFileFromDisk.resolves({
|
||||
url: this.fileUrl,
|
||||
fileRef: this.file,
|
||||
createdBlob: true,
|
||||
})
|
||||
@@ -1181,7 +1169,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
{
|
||||
file: this.newFile,
|
||||
path: this.fileSystemPath,
|
||||
url: this.fileUrl,
|
||||
createdBlob: true,
|
||||
},
|
||||
]
|
||||
@@ -1218,7 +1205,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
element: this.folder,
|
||||
})
|
||||
this.FileStoreHandler.promises.uploadFileFromDisk.resolves({
|
||||
url: this.fileUrl,
|
||||
fileRef: this.newFile,
|
||||
createdBlob: true,
|
||||
})
|
||||
@@ -1254,7 +1240,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
folderId,
|
||||
userId,
|
||||
fileRef: this.newFile,
|
||||
fileStoreUrl: this.fileUrl,
|
||||
source: this.source,
|
||||
createdBlob: true,
|
||||
})
|
||||
@@ -1327,7 +1312,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
folder: this.folder,
|
||||
})
|
||||
|
||||
this.newFileUrl = 'new-file-url'
|
||||
this.newFile = {
|
||||
_id: newFileId,
|
||||
name: 'dummy-upload-filename',
|
||||
@@ -1339,7 +1323,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
overleaf: { history: { id: projectHistoryId } },
|
||||
}
|
||||
this.FileStoreHandler.promises.uploadFileFromDisk.resolves({
|
||||
url: this.newFileUrl,
|
||||
fileRef: this.newFile,
|
||||
createdBlob: true,
|
||||
})
|
||||
@@ -1380,7 +1363,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
{
|
||||
file: this.newFile,
|
||||
path: this.path,
|
||||
url: this.newFileUrl,
|
||||
createdBlob: true,
|
||||
},
|
||||
]
|
||||
@@ -1559,7 +1541,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
this.file = { _id: fileId }
|
||||
this.isNewFile = true
|
||||
this.FileStoreHandler.promises.uploadFileFromDisk.resolves({
|
||||
url: this.fileUrl,
|
||||
fileRef: this.newFile,
|
||||
createdBlob: true,
|
||||
})
|
||||
@@ -1601,7 +1582,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
linkedFileData: this.linkedFileData,
|
||||
userId,
|
||||
fileRef: this.newFile,
|
||||
fileStoreUrl: this.fileUrl,
|
||||
source: this.source,
|
||||
createdBlob: true,
|
||||
}
|
||||
@@ -2567,7 +2547,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
describe('_cleanUpEntity', function () {
|
||||
beforeEach(function () {
|
||||
this.entityId = '4eecaffcbffa66588e000009'
|
||||
this.FileStoreHandler.promises.deleteFile.resolves()
|
||||
this.ProjectEntityUpdateHandler.promises.unsetRootDoc = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
@@ -2590,12 +2569,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should not delete the file from FileStoreHandler', function () {
|
||||
this.FileStoreHandler.promises.deleteFile
|
||||
.calledWith(projectId, this.entityId)
|
||||
.should.equal(false)
|
||||
})
|
||||
|
||||
it('should not attempt to delete from the document updater', function () {
|
||||
this.DocumentUpdaterHandler.promises.deleteDoc.called.should.equal(
|
||||
false
|
||||
@@ -2862,7 +2835,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
.resolves({ lines: this.docLines, rev: this.rev })
|
||||
this.FileWriter.promises.writeLinesToDisk.resolves(this.tmpFilePath)
|
||||
this.FileStoreHandler.promises.uploadFileFromDisk.resolves({
|
||||
url: this.fileStoreUrl,
|
||||
fileRef: this.file,
|
||||
createdBlob: true,
|
||||
})
|
||||
@@ -2927,7 +2899,6 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
{
|
||||
file: this.file,
|
||||
path: this.path,
|
||||
url: this.fileStoreUrl,
|
||||
createdBlob: true,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -30,8 +30,8 @@ describe('ReferencesHandler', function () {
|
||||
{
|
||||
docs: [{ name: 'three.bib', _id: 'ccc' }],
|
||||
fileRefs: [
|
||||
{ name: 'four.bib', _id: 'fff' },
|
||||
{ name: 'five.bib', _id: 'ggg', hash: 'hash' },
|
||||
{ name: 'four.bib', _id: 'fff', hash: 'abc' },
|
||||
{ name: 'five.bib', _id: 'ggg', hash: 'def' },
|
||||
],
|
||||
folders: [],
|
||||
},
|
||||
@@ -50,7 +50,6 @@ describe('ReferencesHandler', function () {
|
||||
filestore: { url: 'http://some.url/filestore' },
|
||||
project_history: { url: 'http://project-history.local' },
|
||||
},
|
||||
filestoreMigrationLevel: 2,
|
||||
}),
|
||||
}))
|
||||
|
||||
@@ -98,9 +97,10 @@ describe('ReferencesHandler', function () {
|
||||
describe('indexAll', function () {
|
||||
beforeEach(function (ctx) {
|
||||
sinon.stub(ctx.handler, '_findBibDocIds').returns(['aaa', 'ccc'])
|
||||
sinon
|
||||
.stub(ctx.handler, '_findBibFileRefs')
|
||||
.returns([{ _id: 'fff' }, { _id: 'ggg', hash: 'hash' }])
|
||||
sinon.stub(ctx.handler, '_findBibFileRefs').returns([
|
||||
{ _id: 'fff', hash: 'abc' },
|
||||
{ _id: 'ggg', hash: 'def' },
|
||||
])
|
||||
sinon.stub(ctx.handler, '_isFullIndex').callsArgWith(1, null, true)
|
||||
ctx.request.post.callsArgWith(
|
||||
1,
|
||||
@@ -164,8 +164,8 @@ describe('ReferencesHandler', function () {
|
||||
expect(arg.json.docUrls).to.deep.equal([
|
||||
`${ctx.settings.apis.docstore.url}/project/${ctx.projectId}/doc/aaa/raw`,
|
||||
`${ctx.settings.apis.docstore.url}/project/${ctx.projectId}/doc/ccc/raw`,
|
||||
`${ctx.settings.apis.filestore.url}/project/${ctx.projectId}/file/fff?from=bibFileUrls`,
|
||||
`${ctx.settings.apis.filestore.url}/project/${ctx.projectId}/file/ggg?from=bibFileUrls`,
|
||||
`${ctx.settings.apis.project_history.url}/project/${ctx.historyId}/blob/abc`,
|
||||
`${ctx.settings.apis.project_history.url}/project/${ctx.historyId}/blob/def`,
|
||||
])
|
||||
expect(arg.json.sourceURLs.length).to.equal(4)
|
||||
expect(arg.json.sourceURLs).to.deep.equal([
|
||||
@@ -176,11 +176,10 @@ describe('ReferencesHandler', function () {
|
||||
url: `${ctx.settings.apis.docstore.url}/project/${ctx.projectId}/doc/ccc/raw`,
|
||||
},
|
||||
{
|
||||
url: `${ctx.settings.apis.filestore.url}/project/${ctx.projectId}/file/fff?from=bibFileUrls`,
|
||||
url: `${ctx.settings.apis.project_history.url}/project/${ctx.historyId}/blob/abc`,
|
||||
},
|
||||
{
|
||||
url: `${ctx.settings.apis.project_history.url}/project/${ctx.historyId}/blob/hash`,
|
||||
fallbackURL: `${ctx.settings.apis.filestore.url}/project/${ctx.projectId}/file/ggg?from=bibFileUrls`,
|
||||
url: `${ctx.settings.apis.project_history.url}/project/${ctx.historyId}/blob/def`,
|
||||
},
|
||||
])
|
||||
expect(arg.json.fullIndex).to.equal(true)
|
||||
|
||||
@@ -39,7 +39,6 @@ describe('SplitTestHandler', function () {
|
||||
}
|
||||
this.SplitTestCache.get.resolves(this.cachedSplitTests)
|
||||
this.Settings = {
|
||||
filestoreMigrationLevel: 0,
|
||||
moduleImportSequence: [],
|
||||
overleaf: {},
|
||||
devToolbar: {
|
||||
|
||||
@@ -57,7 +57,6 @@ describe('TpdsUpdateSender', function () {
|
||||
url: projectHistoryUrl,
|
||||
},
|
||||
},
|
||||
filestoreMigrationLevel: true,
|
||||
}
|
||||
const getUsers = sinon.stub()
|
||||
getUsers
|
||||
@@ -116,59 +115,6 @@ describe('TpdsUpdateSender', function () {
|
||||
this.settings.apis.tpdsworker = { url: 'http://tpdsworker' }
|
||||
})
|
||||
|
||||
it('queues a post the file with user and file id', async function () {
|
||||
const fileId = '4545345'
|
||||
const hash = undefined
|
||||
const historyId = 91525
|
||||
const path = '/some/path/here.jpg'
|
||||
|
||||
await this.TpdsUpdateSender.promises.addFile({
|
||||
projectId,
|
||||
historyId,
|
||||
fileId,
|
||||
hash,
|
||||
path,
|
||||
projectName,
|
||||
})
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: userId,
|
||||
method: 'pipeStreamFrom',
|
||||
job: {
|
||||
method: 'post',
|
||||
streamOrigin: `${filestoreUrl}/project/${projectId}/file/${fileId}?from=tpdsAddFile`,
|
||||
uri: `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
|
||||
projectName
|
||||
)}${encodeURIComponent(path)}`,
|
||||
headers: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: collaberatorRef,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: readOnlyRef,
|
||||
job: {},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('queues a post the file with user and file id and hash', async function () {
|
||||
const fileId = '4545345'
|
||||
const hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
@@ -193,7 +139,6 @@ describe('TpdsUpdateSender', function () {
|
||||
job: {
|
||||
method: 'post',
|
||||
streamOrigin: `${projectHistoryUrl}/project/${historyId}/blob/${hash}`,
|
||||
streamFallback: `${filestoreUrl}/project/${projectId}/file/${fileId}?from=tpdsAddFile`,
|
||||
uri: `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
|
||||
projectName
|
||||
)}${encodeURIComponent(path)}`,
|
||||
|
||||
@@ -61,7 +61,6 @@ describe('ProjectUploadManager', function () {
|
||||
{
|
||||
file: this.file,
|
||||
path: `/${this.file.name}`,
|
||||
url: this.fileStoreUrl,
|
||||
createdBlob: true,
|
||||
},
|
||||
]
|
||||
@@ -101,7 +100,6 @@ describe('ProjectUploadManager', function () {
|
||||
promises: {
|
||||
uploadFileFromDiskWithHistoryId: sinon.stub().resolves({
|
||||
fileRef: this.file,
|
||||
url: this.fileStoreUrl,
|
||||
createdBlob: true,
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ describe('Features', function () {
|
||||
this.Features = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'@overleaf/settings': (this.settings = {
|
||||
filestoreMigrationLevel: 0,
|
||||
moduleImportSequence: [],
|
||||
enabledLinkedFileTypes: [],
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user