[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:
Jakob Ackermann
2025-09-01 11:43:37 +02:00
committed by Copybot
parent 21d3879574
commit 319a542e8d
73 changed files with 1481 additions and 3587 deletions
@@ -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: [],
}),