mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-30 12:24:25 +02:00
[web] last infrastructure conversions GitOrigin-RevId: ad1aff9b7df0610ed0303157d9e2c8032f32c02b
381 lines
11 KiB
JavaScript
381 lines
11 KiB
JavaScript
import { beforeAll, beforeEach, describe, it, vi, expect } from 'vitest'
|
|
import sinon from 'sinon'
|
|
import mongodb from 'mongodb-legacy'
|
|
import {
|
|
cleanupTestDatabase,
|
|
db,
|
|
waitForDb,
|
|
} from '../../../../app/src/infrastructure/mongodb.mjs'
|
|
|
|
const { ObjectId } = mongodb
|
|
|
|
const MODULE_PATH = '../../../../app/src/Features/History/HistoryManager'
|
|
|
|
const GLOBAL_BLOBS = [
|
|
'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391',
|
|
'02426c2b3a484003ca42ed52b374b7907b757d12',
|
|
]
|
|
|
|
describe('HistoryManager', function () {
|
|
beforeAll(async function () {
|
|
await waitForDb()
|
|
})
|
|
beforeAll(cleanupTestDatabase)
|
|
beforeAll(async function () {
|
|
await db.projectHistoryGlobalBlobs.insertMany(
|
|
GLOBAL_BLOBS.map(sha => ({
|
|
_id: sha,
|
|
byteLength: 0,
|
|
stringLength: 0,
|
|
}))
|
|
)
|
|
})
|
|
|
|
beforeEach(async function (ctx) {
|
|
ctx.user_id = 'user-id-123'
|
|
ctx.historyId = new ObjectId().toString()
|
|
ctx.AuthenticationController = {
|
|
getLoggedInUserId: sinon.stub().returns(ctx.user_id),
|
|
}
|
|
ctx.FetchUtils = {
|
|
fetchJson: sinon.stub(),
|
|
fetchNothing: sinon.stub().resolves(),
|
|
}
|
|
ctx.projectHistoryUrl = 'http://project_history.example.com'
|
|
ctx.v1HistoryUrl = 'http://v1_history.example.com'
|
|
ctx.v1HistoryUser = 'system'
|
|
ctx.v1HistoryPassword = 'verysecret'
|
|
ctx.settings = {
|
|
apis: {
|
|
filestore: {
|
|
url: 'http://filestore.example.com',
|
|
},
|
|
project_history: {
|
|
url: ctx.projectHistoryUrl,
|
|
},
|
|
v1_history: {
|
|
url: ctx.v1HistoryUrl,
|
|
user: ctx.v1HistoryUser,
|
|
pass: ctx.v1HistoryPassword,
|
|
buckets: {
|
|
globalBlobs: 'globalBlobs',
|
|
projectBlobs: 'projectBlobs',
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
ctx.UserGetter = {
|
|
promises: {
|
|
getUsersByV1Ids: sinon.stub(),
|
|
getUsers: sinon.stub(),
|
|
},
|
|
}
|
|
|
|
ctx.project = {
|
|
overleaf: {
|
|
history: {
|
|
id: ctx.historyId,
|
|
},
|
|
},
|
|
}
|
|
|
|
ctx.ProjectGetter = {
|
|
promises: {
|
|
getProject: sinon.stub().resolves(ctx.project),
|
|
},
|
|
}
|
|
|
|
ctx.HistoryBackupDeletionHandler = {
|
|
deleteProject: sinon.stub().resolves(),
|
|
}
|
|
|
|
vi.doMock('../../../../app/src/infrastructure/mongodb', () => ({
|
|
ObjectId,
|
|
db,
|
|
waitForDb,
|
|
}))
|
|
|
|
vi.doMock('@overleaf/fetch-utils', () => ctx.FetchUtils)
|
|
|
|
vi.doMock('@overleaf/settings', () => ({
|
|
default: ctx.settings,
|
|
}))
|
|
|
|
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
|
default: ctx.UserGetter,
|
|
}))
|
|
|
|
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
|
|
default: ctx.ProjectGetter,
|
|
}))
|
|
|
|
vi.doMock(
|
|
'../../../../app/src/Features/History/HistoryBackupDeletionHandler',
|
|
() => ({
|
|
default: ctx.HistoryBackupDeletionHandler,
|
|
})
|
|
)
|
|
|
|
ctx.HistoryManager = (await import(MODULE_PATH)).default
|
|
})
|
|
|
|
describe('getFilestoreBlobURL', function () {
|
|
beforeEach(async function (ctx) {
|
|
await ctx.HistoryManager.loadGlobalBlobsPromise
|
|
})
|
|
it('should return a global blob location', function (ctx) {
|
|
for (const sha of GLOBAL_BLOBS) {
|
|
expect(ctx.HistoryManager.getFilestoreBlobURL('42', sha)).to.equal(
|
|
`${ctx.settings.apis.filestore.url}/history/global/hash/${sha}`
|
|
)
|
|
}
|
|
})
|
|
it('should return a project blob location for a v1 project', function (ctx) {
|
|
const historyId = 42
|
|
const sha = '6ddfa0578a67fe5ad6623a8665ec9aafce1eb5ca'
|
|
expect(ctx.HistoryManager.getFilestoreBlobURL(historyId, sha)).to.equal(
|
|
`${ctx.settings.apis.filestore.url}/history/project/${historyId}/hash/${sha}`
|
|
)
|
|
})
|
|
it('should return a project blob location for a mongo project', function (ctx) {
|
|
const historyId = '424242424242424242424242'
|
|
const sha = '6ddfa0578a67fe5ad6623a8665ec9aafce1eb5ca'
|
|
expect(ctx.HistoryManager.getFilestoreBlobURL(historyId, sha)).to.equal(
|
|
`${ctx.settings.apis.filestore.url}/history/project/${historyId}/hash/${sha}`
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('initializeProject', function () {
|
|
beforeEach(function (ctx) {
|
|
ctx.settings.apis.project_history.initializeHistoryForNewProjects = true
|
|
})
|
|
|
|
describe('project history returns a successful response', function () {
|
|
beforeEach(async function (ctx) {
|
|
ctx.FetchUtils.fetchJson.resolves({ project: { id: ctx.historyId } })
|
|
ctx.result = await ctx.HistoryManager.promises.initializeProject(
|
|
ctx.historyId
|
|
)
|
|
})
|
|
|
|
it('should call the project history api', function (ctx) {
|
|
ctx.FetchUtils.fetchJson.should.have.been.calledWithMatch(
|
|
`${ctx.settings.apis.project_history.url}/project`,
|
|
{ method: 'POST' }
|
|
)
|
|
})
|
|
|
|
it('should return the overleaf id', function (ctx) {
|
|
expect(ctx.result).to.equal(ctx.historyId)
|
|
})
|
|
})
|
|
|
|
describe('project history returns a response without the project id', function () {
|
|
it('should throw an error', async function (ctx) {
|
|
ctx.FetchUtils.fetchJson.resolves({ project: {} })
|
|
await expect(
|
|
ctx.HistoryManager.promises.initializeProject(ctx.historyId)
|
|
).to.be.rejected
|
|
})
|
|
})
|
|
|
|
describe('project history errors', function () {
|
|
it('should propagate the error', async function (ctx) {
|
|
ctx.FetchUtils.fetchJson.rejects(new Error('problem connecting'))
|
|
await expect(
|
|
ctx.HistoryManager.promises.initializeProject(ctx.historyId)
|
|
).to.be.rejected
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('injectUserDetails', function () {
|
|
beforeEach(function (ctx) {
|
|
ctx.user1 = {
|
|
_id: (ctx.user_id1 = '123456'),
|
|
first_name: 'Jane',
|
|
last_name: 'Doe',
|
|
email: 'jane@example.com',
|
|
overleaf: { id: 5011 },
|
|
}
|
|
ctx.user1_view = {
|
|
id: ctx.user_id1,
|
|
first_name: 'Jane',
|
|
last_name: 'Doe',
|
|
email: 'jane@example.com',
|
|
}
|
|
ctx.user2 = {
|
|
_id: (ctx.user_id2 = 'abcdef'),
|
|
first_name: 'John',
|
|
last_name: 'Doe',
|
|
email: 'john@example.com',
|
|
}
|
|
ctx.user2_view = {
|
|
id: ctx.user_id2,
|
|
first_name: 'John',
|
|
last_name: 'Doe',
|
|
email: 'john@example.com',
|
|
}
|
|
ctx.UserGetter.promises.getUsersByV1Ids.resolves([ctx.user1])
|
|
ctx.UserGetter.promises.getUsers.resolves([ctx.user1, ctx.user2])
|
|
})
|
|
|
|
describe('with a diff', function () {
|
|
it('should turn user_ids into user objects', async function (ctx) {
|
|
const diff = await ctx.HistoryManager.promises.injectUserDetails({
|
|
diff: [
|
|
{
|
|
i: 'foo',
|
|
meta: {
|
|
users: [ctx.user_id1],
|
|
},
|
|
},
|
|
{
|
|
i: 'bar',
|
|
meta: {
|
|
users: [ctx.user_id2],
|
|
},
|
|
},
|
|
],
|
|
})
|
|
expect(diff.diff[0].meta.users).to.deep.equal([ctx.user1_view])
|
|
expect(diff.diff[1].meta.users).to.deep.equal([ctx.user2_view])
|
|
})
|
|
|
|
it('should handle v1 user ids', async function (ctx) {
|
|
const diff = await ctx.HistoryManager.promises.injectUserDetails({
|
|
diff: [
|
|
{
|
|
i: 'foo',
|
|
meta: {
|
|
users: [5011],
|
|
},
|
|
},
|
|
{
|
|
i: 'bar',
|
|
meta: {
|
|
users: [ctx.user_id2],
|
|
},
|
|
},
|
|
],
|
|
})
|
|
expect(diff.diff[0].meta.users).to.deep.equal([ctx.user1_view])
|
|
expect(diff.diff[1].meta.users).to.deep.equal([ctx.user2_view])
|
|
})
|
|
|
|
it('should leave user objects', async function (ctx) {
|
|
const diff = await ctx.HistoryManager.promises.injectUserDetails({
|
|
diff: [
|
|
{
|
|
i: 'foo',
|
|
meta: {
|
|
users: [ctx.user1_view],
|
|
},
|
|
},
|
|
{
|
|
i: 'bar',
|
|
meta: {
|
|
users: [ctx.user_id2],
|
|
},
|
|
},
|
|
],
|
|
})
|
|
expect(diff.diff[0].meta.users).to.deep.equal([ctx.user1_view])
|
|
expect(diff.diff[1].meta.users).to.deep.equal([ctx.user2_view])
|
|
})
|
|
|
|
it('should handle a binary diff marker', async function (ctx) {
|
|
const diff = await ctx.HistoryManager.promises.injectUserDetails({
|
|
diff: { binary: true },
|
|
})
|
|
expect(diff.diff.binary).to.be.true
|
|
})
|
|
})
|
|
|
|
describe('with a list of updates', function () {
|
|
it('should turn user_ids into user objects', async function (ctx) {
|
|
const updates = await ctx.HistoryManager.promises.injectUserDetails({
|
|
updates: [
|
|
{
|
|
fromV: 5,
|
|
toV: 8,
|
|
meta: {
|
|
users: [ctx.user_id1],
|
|
},
|
|
},
|
|
{
|
|
fromV: 4,
|
|
toV: 5,
|
|
meta: {
|
|
users: [ctx.user_id2],
|
|
},
|
|
},
|
|
],
|
|
})
|
|
expect(updates.updates[0].meta.users).to.deep.equal([ctx.user1_view])
|
|
expect(updates.updates[1].meta.users).to.deep.equal([ctx.user2_view])
|
|
})
|
|
|
|
it('should leave user objects', async function (ctx) {
|
|
const updates = await ctx.HistoryManager.promises.injectUserDetails({
|
|
updates: [
|
|
{
|
|
fromV: 5,
|
|
toV: 8,
|
|
meta: {
|
|
users: [ctx.user1_view],
|
|
},
|
|
},
|
|
{
|
|
fromV: 4,
|
|
toV: 5,
|
|
meta: {
|
|
users: [ctx.user_id2],
|
|
},
|
|
},
|
|
],
|
|
})
|
|
expect(updates.updates[0].meta.users).to.deep.equal([ctx.user1_view])
|
|
expect(updates.updates[1].meta.users).to.deep.equal([ctx.user2_view])
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('deleteProject', function () {
|
|
const projectId = new ObjectId()
|
|
const historyId = new ObjectId()
|
|
|
|
beforeEach(async function (ctx) {
|
|
await ctx.HistoryManager.promises.deleteProject(projectId, historyId)
|
|
})
|
|
|
|
it('should call the project-history service', async function (ctx) {
|
|
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWith(
|
|
`${ctx.projectHistoryUrl}/project/${projectId}`,
|
|
{ method: 'DELETE' }
|
|
)
|
|
})
|
|
|
|
it('should call the v1-history service', async function (ctx) {
|
|
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWith(
|
|
`${ctx.v1HistoryUrl}/projects/${historyId}`,
|
|
{
|
|
method: 'DELETE',
|
|
basicAuth: {
|
|
user: ctx.v1HistoryUser,
|
|
password: ctx.v1HistoryPassword,
|
|
},
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should call the history-backup-deletion service', async function (ctx) {
|
|
expect(
|
|
ctx.HistoryBackupDeletionHandler.deleteProject
|
|
).to.have.been.calledWith(projectId)
|
|
})
|
|
})
|
|
})
|