| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- import { expect } from 'chai'
- import sinon from 'sinon'
- import SandboxedModule from 'sandboxed-module'
- import mongodb from 'mongodb-legacy'
- import {
- cleanupTestDatabase,
- db,
- waitForDb,
- } from '../../../../app/src/infrastructure/mongodb.js'
- const { ObjectId } = mongodb
- const MODULE_PATH = '../../../../app/src/Features/History/HistoryManager'
- const GLOBAL_BLOBS = [
- 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391',
- '02426c2b3a484003ca42ed52b374b7907b757d12',
- ]
- describe('HistoryManager', function () {
- before(async function () {
- await waitForDb()
- })
- before(cleanupTestDatabase)
- before(async function () {
- await db.projectHistoryGlobalBlobs.insertMany(
- GLOBAL_BLOBS.map(sha => ({
- _id: sha,
- byteLength: 0,
- stringLength: 0,
- }))
- )
- })
- beforeEach(function () {
- this.user_id = 'user-id-123'
- this.historyId = new ObjectId().toString()
- this.AuthenticationController = {
- getLoggedInUserId: sinon.stub().returns(this.user_id),
- }
- this.FetchUtils = {
- fetchJson: sinon.stub(),
- fetchNothing: sinon.stub().resolves(),
- }
- this.projectHistoryUrl = 'http://project_history.example.com'
- this.v1HistoryUrl = 'http://v1_history.example.com'
- this.v1HistoryUser = 'system'
- this.v1HistoryPassword = 'verysecret'
- this.settings = {
- apis: {
- filestore: {
- url: 'http://filestore.example.com',
- },
- project_history: {
- url: this.projectHistoryUrl,
- },
- v1_history: {
- url: this.v1HistoryUrl,
- user: this.v1HistoryUser,
- pass: this.v1HistoryPassword,
- buckets: {
- globalBlobs: 'globalBlobs',
- projectBlobs: 'projectBlobs',
- },
- },
- },
- }
- this.UserGetter = {
- promises: {
- getUsersByV1Ids: sinon.stub(),
- getUsers: sinon.stub(),
- },
- }
- this.project = {
- overleaf: {
- history: {
- id: this.historyId,
- },
- },
- }
- this.ProjectGetter = {
- promises: {
- getProject: sinon.stub().resolves(this.project),
- },
- }
- this.HistoryBackupDeletionHandler = {
- deleteProject: sinon.stub().resolves(),
- }
- this.HistoryManager = SandboxedModule.require(MODULE_PATH, {
- requires: {
- '../../infrastructure/mongodb': { ObjectId, db, waitForDb },
- '@overleaf/fetch-utils': this.FetchUtils,
- '@overleaf/settings': this.settings,
- '../User/UserGetter': this.UserGetter,
- '../Project/ProjectGetter': this.ProjectGetter,
- './HistoryBackupDeletionHandler': this.HistoryBackupDeletionHandler,
- },
- })
- })
- describe('getFilestoreBlobURL', function () {
- beforeEach(async function () {
- await this.HistoryManager.loadGlobalBlobsPromise
- })
- it('should return a global blob location', function () {
- for (const sha of GLOBAL_BLOBS) {
- expect(this.HistoryManager.getFilestoreBlobURL('42', sha)).to.equal(
- `${this.settings.apis.filestore.url}/history/global/hash/${sha}`
- )
- }
- })
- it('should return a project blob location for a v1 project', function () {
- const historyId = 42
- const sha = '6ddfa0578a67fe5ad6623a8665ec9aafce1eb5ca'
- expect(this.HistoryManager.getFilestoreBlobURL(historyId, sha)).to.equal(
- `${this.settings.apis.filestore.url}/history/project/${historyId}/hash/${sha}`
- )
- })
- it('should return a project blob location for a mongo project', function () {
- const historyId = '424242424242424242424242'
- const sha = '6ddfa0578a67fe5ad6623a8665ec9aafce1eb5ca'
- expect(this.HistoryManager.getFilestoreBlobURL(historyId, sha)).to.equal(
- `${this.settings.apis.filestore.url}/history/project/${historyId}/hash/${sha}`
- )
- })
- })
- describe('initializeProject', function () {
- beforeEach(function () {
- this.settings.apis.project_history.initializeHistoryForNewProjects = true
- })
- describe('project history returns a successful response', function () {
- beforeEach(async function () {
- this.FetchUtils.fetchJson.resolves({ project: { id: this.historyId } })
- this.result = await this.HistoryManager.promises.initializeProject(
- this.historyId
- )
- })
- it('should call the project history api', function () {
- this.FetchUtils.fetchJson.should.have.been.calledWithMatch(
- `${this.settings.apis.project_history.url}/project`,
- { method: 'POST' }
- )
- })
- it('should return the overleaf id', function () {
- expect(this.result).to.equal(this.historyId)
- })
- })
- describe('project history returns a response without the project id', function () {
- it('should throw an error', async function () {
- this.FetchUtils.fetchJson.resolves({ project: {} })
- await expect(
- this.HistoryManager.promises.initializeProject(this.historyId)
- ).to.be.rejected
- })
- })
- describe('project history errors', function () {
- it('should propagate the error', async function () {
- this.FetchUtils.fetchJson.rejects(new Error('problem connecting'))
- await expect(
- this.HistoryManager.promises.initializeProject(this.historyId)
- ).to.be.rejected
- })
- })
- })
- describe('injectUserDetails', function () {
- beforeEach(function () {
- this.user1 = {
- _id: (this.user_id1 = '123456'),
- first_name: 'Jane',
- last_name: 'Doe',
- email: 'jane@example.com',
- overleaf: { id: 5011 },
- }
- this.user1_view = {
- id: this.user_id1,
- first_name: 'Jane',
- last_name: 'Doe',
- email: 'jane@example.com',
- }
- this.user2 = {
- _id: (this.user_id2 = 'abcdef'),
- first_name: 'John',
- last_name: 'Doe',
- email: 'john@example.com',
- }
- this.user2_view = {
- id: this.user_id2,
- first_name: 'John',
- last_name: 'Doe',
- email: 'john@example.com',
- }
- this.UserGetter.promises.getUsersByV1Ids.resolves([this.user1])
- this.UserGetter.promises.getUsers.resolves([this.user1, this.user2])
- })
- describe('with a diff', function () {
- it('should turn user_ids into user objects', async function () {
- const diff = await this.HistoryManager.promises.injectUserDetails({
- diff: [
- {
- i: 'foo',
- meta: {
- users: [this.user_id1],
- },
- },
- {
- i: 'bar',
- meta: {
- users: [this.user_id2],
- },
- },
- ],
- })
- expect(diff.diff[0].meta.users).to.deep.equal([this.user1_view])
- expect(diff.diff[1].meta.users).to.deep.equal([this.user2_view])
- })
- it('should handle v1 user ids', async function () {
- const diff = await this.HistoryManager.promises.injectUserDetails({
- diff: [
- {
- i: 'foo',
- meta: {
- users: [5011],
- },
- },
- {
- i: 'bar',
- meta: {
- users: [this.user_id2],
- },
- },
- ],
- })
- expect(diff.diff[0].meta.users).to.deep.equal([this.user1_view])
- expect(diff.diff[1].meta.users).to.deep.equal([this.user2_view])
- })
- it('should leave user objects', async function () {
- const diff = await this.HistoryManager.promises.injectUserDetails({
- diff: [
- {
- i: 'foo',
- meta: {
- users: [this.user1_view],
- },
- },
- {
- i: 'bar',
- meta: {
- users: [this.user_id2],
- },
- },
- ],
- })
- expect(diff.diff[0].meta.users).to.deep.equal([this.user1_view])
- expect(diff.diff[1].meta.users).to.deep.equal([this.user2_view])
- })
- it('should handle a binary diff marker', async function () {
- const diff = await this.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 () {
- const updates = await this.HistoryManager.promises.injectUserDetails({
- updates: [
- {
- fromV: 5,
- toV: 8,
- meta: {
- users: [this.user_id1],
- },
- },
- {
- fromV: 4,
- toV: 5,
- meta: {
- users: [this.user_id2],
- },
- },
- ],
- })
- expect(updates.updates[0].meta.users).to.deep.equal([this.user1_view])
- expect(updates.updates[1].meta.users).to.deep.equal([this.user2_view])
- })
- it('should leave user objects', async function () {
- const updates = await this.HistoryManager.promises.injectUserDetails({
- updates: [
- {
- fromV: 5,
- toV: 8,
- meta: {
- users: [this.user1_view],
- },
- },
- {
- fromV: 4,
- toV: 5,
- meta: {
- users: [this.user_id2],
- },
- },
- ],
- })
- expect(updates.updates[0].meta.users).to.deep.equal([this.user1_view])
- expect(updates.updates[1].meta.users).to.deep.equal([this.user2_view])
- })
- })
- })
- describe('deleteProject', function () {
- const projectId = new ObjectId()
- const historyId = new ObjectId()
- beforeEach(async function () {
- await this.HistoryManager.promises.deleteProject(projectId, historyId)
- })
- it('should call the project-history service', async function () {
- expect(this.FetchUtils.fetchNothing).to.have.been.calledWith(
- `${this.projectHistoryUrl}/project/${projectId}`,
- { method: 'DELETE' }
- )
- })
- it('should call the v1-history service', async function () {
- expect(this.FetchUtils.fetchNothing).to.have.been.calledWith(
- `${this.v1HistoryUrl}/projects/${historyId}`,
- {
- method: 'DELETE',
- basicAuth: {
- user: this.v1HistoryUser,
- password: this.v1HistoryPassword,
- },
- }
- )
- })
- it('should call the history-backup-deletion service', async function () {
- expect(
- this.HistoryBackupDeletionHandler.deleteProject
- ).to.have.been.calledWith(projectId)
- })
- })
- })
|