Convert tests to ESM

GitOrigin-RevId: 20585e01dee90e691476a0d47fd5c63b0412e4a6
This commit is contained in:
Andrew Rumble
2025-10-23 08:06:15 +00:00
committed by Copybot
parent f02f6475ac
commit b7c883ac38
37 changed files with 9076 additions and 8299 deletions
File diff suppressed because it is too large Load Diff
@@ -1,36 +1,41 @@
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { expect } = require('chai')
const { ObjectId } = require('mongodb-legacy')
const Errors = require('../../../../app/src/Features/Errors/Errors')
const ProjectHelper = require('../../../../app/src/Features/Project/ProjectHelper')
import { vi, expect } from 'vitest'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
import ProjectHelper from '../../../../app/src/Features/Project/ProjectHelper.js'
const { ObjectId } = mongodb
const MODULE_PATH = '../../../../app/src/Features/Project/ProjectDetailsHandler'
vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
vi.importActual('../../../../app/src/Features/Errors/Errors.js')
)
describe('ProjectDetailsHandler', function () {
beforeEach(function () {
this.user = {
beforeEach(async function (ctx) {
ctx.user = {
_id: new ObjectId(),
email: 'user@example.com',
features: 'mock-features',
}
this.collaborator = {
ctx.collaborator = {
_id: new ObjectId(),
email: 'collaborator@example.com',
}
this.project = {
ctx.project = {
_id: new ObjectId(),
name: 'project',
description: 'this is a great project',
something: 'should not exist',
compiler: 'latexxxxxx',
owner_ref: this.user._id,
collaberator_refs: [this.collaborator._id],
owner_ref: ctx.user._id,
collaberator_refs: [ctx.collaborator._id],
}
this.ProjectGetter = {
ctx.ProjectGetter = {
promises: {
getProjectWithoutDocLines: sinon.stub().resolves(this.project),
getProject: sinon.stub().resolves(this.project),
getProjectWithoutDocLines: sinon.stub().resolves(ctx.project),
getProject: sinon.stub().resolves(ctx.project),
findAllUsersProjects: sinon.stub().resolves({
owned: [],
readAndWrite: [],
@@ -40,200 +45,221 @@ describe('ProjectDetailsHandler', function () {
}),
},
}
this.ProjectModelUpdateQuery = {
ctx.ProjectModelUpdateQuery = {
exec: sinon.stub().resolves(),
}
this.ProjectModel = {
updateOne: sinon.stub().returns(this.ProjectModelUpdateQuery),
ctx.ProjectModel = {
updateOne: sinon.stub().returns(ctx.ProjectModelUpdateQuery),
}
this.UserGetter = {
ctx.UserGetter = {
promises: {
getUser: sinon.stub().resolves(this.user),
getUser: sinon.stub().resolves(ctx.user),
},
}
this.TpdsUpdateSender = {
ctx.TpdsUpdateSender = {
promises: {
moveEntity: sinon.stub().resolves(),
},
}
this.TokenGenerator = {
ctx.TokenGenerator = {
readAndWriteToken: sinon.stub(),
promises: {
generateUniqueReadOnlyToken: sinon.stub(),
},
}
this.settings = {
ctx.settings = {
defaultFeatures: 'default-features',
}
this.handler = SandboxedModule.require(MODULE_PATH, {
requires: {
'./ProjectHelper': ProjectHelper,
'./ProjectGetter': this.ProjectGetter,
'../../models/Project': {
Project: this.ProjectModel,
},
'../User/UserGetter': this.UserGetter,
'../ThirdPartyDataStore/TpdsUpdateSender': this.TpdsUpdateSender,
'../TokenGenerator/TokenGenerator': this.TokenGenerator,
'@overleaf/settings': this.settings,
},
})
vi.doMock('../../../../app/src/Features/Project/ProjectHelper', () => ({
default: ProjectHelper,
}))
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
default: ctx.ProjectGetter,
}))
vi.doMock('../../../../app/src/models/Project', () => ({
Project: ctx.ProjectModel,
}))
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
default: ctx.UserGetter,
}))
vi.doMock(
'../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender',
() => ({
default: ctx.TpdsUpdateSender,
})
)
vi.doMock(
'../../../../app/src/Features/TokenGenerator/TokenGenerator',
() => ({
default: ctx.TokenGenerator,
})
)
vi.doMock('@overleaf/settings', () => ({
default: ctx.settings,
}))
ctx.handler = (await import(MODULE_PATH)).default
})
describe('getDetails', function () {
it('should find the project and owner', async function () {
const details = await this.handler.promises.getDetails(this.project._id)
details.name.should.equal(this.project.name)
details.description.should.equal(this.project.description)
details.compiler.should.equal(this.project.compiler)
details.features.should.equal(this.user.features)
it('should find the project and owner', async function (ctx) {
const details = await ctx.handler.promises.getDetails(ctx.project._id)
details.name.should.equal(ctx.project.name)
details.description.should.equal(ctx.project.description)
details.compiler.should.equal(ctx.project.compiler)
details.features.should.equal(ctx.user.features)
expect(details.something).to.be.undefined
})
it('should find overleaf metadata if it exists', async function () {
this.project.overleaf = { id: 'id' }
const details = await this.handler.promises.getDetails(this.project._id)
details.overleaf.should.equal(this.project.overleaf)
it('should find overleaf metadata if it exists', async function (ctx) {
ctx.project.overleaf = { id: 'id' }
const details = await ctx.handler.promises.getDetails(ctx.project._id)
details.overleaf.should.equal(ctx.project.overleaf)
expect(details.something).to.be.undefined
})
it('should return an error for a non-existent project', async function () {
this.ProjectGetter.promises.getProject.resolves(null)
it('should return an error for a non-existent project', async function (ctx) {
ctx.ProjectGetter.promises.getProject.resolves(null)
await expect(
this.handler.promises.getDetails('0123456789012345678901234')
ctx.handler.promises.getDetails('0123456789012345678901234')
).to.be.rejectedWith(Errors.NotFoundError)
})
it('should return the default features if no owner found', async function () {
this.UserGetter.promises.getUser.resolves(null)
const details = await this.handler.promises.getDetails(this.project._id)
details.features.should.equal(this.settings.defaultFeatures)
it('should return the default features if no owner found', async function (ctx) {
ctx.UserGetter.promises.getUser.resolves(null)
const details = await ctx.handler.promises.getDetails(ctx.project._id)
details.features.should.equal(ctx.settings.defaultFeatures)
})
it('should rethrow any error', async function () {
this.ProjectGetter.promises.getProject.rejects(new Error('boom'))
await expect(this.handler.promises.getDetails(this.project._id)).to.be
it('should rethrow any error', async function (ctx) {
ctx.ProjectGetter.promises.getProject.rejects(new Error('boom'))
await expect(ctx.handler.promises.getDetails(ctx.project._id)).to.be
.rejected
})
})
describe('getProjectDescription', function () {
it('should make a call to mongo just for the description', async function () {
this.ProjectGetter.promises.getProject.resolves()
await this.handler.promises.getProjectDescription(this.project._id)
expect(this.ProjectGetter.promises.getProject).to.have.been.calledWith(
this.project._id,
it('should make a call to mongo just for the description', async function (ctx) {
ctx.ProjectGetter.promises.getProject.resolves()
await ctx.handler.promises.getProjectDescription(ctx.project._id)
expect(ctx.ProjectGetter.promises.getProject).to.have.been.calledWith(
ctx.project._id,
{ description: true }
)
})
it('should return what the mongo call returns', async function () {
it('should return what the mongo call returns', async function (ctx) {
const expectedDescription = 'cool project'
this.ProjectGetter.promises.getProject.resolves({
ctx.ProjectGetter.promises.getProject.resolves({
description: expectedDescription,
})
const description = await this.handler.promises.getProjectDescription(
this.project._id
const description = await ctx.handler.promises.getProjectDescription(
ctx.project._id
)
expect(description).to.equal(expectedDescription)
})
})
describe('setProjectDescription', function () {
beforeEach(function () {
this.description = 'updated teh description'
beforeEach(function (ctx) {
ctx.description = 'updated teh description'
})
it('should update the project detials', async function () {
await this.handler.promises.setProjectDescription(
this.project._id,
this.description
it('should update the project detials', async function (ctx) {
await ctx.handler.promises.setProjectDescription(
ctx.project._id,
ctx.description
)
expect(this.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: this.project._id },
{ description: this.description }
expect(ctx.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: ctx.project._id },
{ description: ctx.description }
)
})
})
describe('renameProject', function () {
beforeEach(function () {
this.newName = 'new name here'
beforeEach(function (ctx) {
ctx.newName = 'new name here'
})
it('should update the project with the new name', async function () {
await this.handler.promises.renameProject(this.project._id, this.newName)
expect(this.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: this.project._id },
{ name: this.newName }
it('should update the project with the new name', async function (ctx) {
await ctx.handler.promises.renameProject(ctx.project._id, ctx.newName)
expect(ctx.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: ctx.project._id },
{ name: ctx.newName }
)
})
it('should tell the TpdsUpdateSender', async function () {
await this.handler.promises.renameProject(this.project._id, this.newName)
expect(this.TpdsUpdateSender.promises.moveEntity).to.have.been.calledWith(
{
projectId: this.project._id,
projectName: this.project.name,
newProjectName: this.newName,
}
)
it('should tell the TpdsUpdateSender', async function (ctx) {
await ctx.handler.promises.renameProject(ctx.project._id, ctx.newName)
expect(ctx.TpdsUpdateSender.promises.moveEntity).to.have.been.calledWith({
projectId: ctx.project._id,
projectName: ctx.project.name,
newProjectName: ctx.newName,
})
})
it('should not do anything with an invalid name', async function () {
await expect(this.handler.promises.renameProject(this.project._id)).to.be
it('should not do anything with an invalid name', async function (ctx) {
await expect(ctx.handler.promises.renameProject(ctx.project._id)).to.be
.rejected
expect(this.TpdsUpdateSender.promises.moveEntity).not.to.have.been.called
expect(this.ProjectModel.updateOne).not.to.have.been.called
expect(ctx.TpdsUpdateSender.promises.moveEntity).not.to.have.been.called
expect(ctx.ProjectModel.updateOne).not.to.have.been.called
})
it('should trim whitespace around name', async function () {
await this.handler.promises.renameProject(
this.project._id,
` ${this.newName} `
it('should trim whitespace around name', async function (ctx) {
await ctx.handler.promises.renameProject(
ctx.project._id,
` ${ctx.newName} `
)
expect(this.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: this.project._id },
{ name: this.newName }
expect(ctx.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: ctx.project._id },
{ name: ctx.newName }
)
})
})
describe('validateProjectName', function () {
it('should reject undefined names', async function () {
await expect(this.handler.promises.validateProjectName(undefined)).to.be
it('should reject undefined names', async function (ctx) {
await expect(ctx.handler.promises.validateProjectName(undefined)).to.be
.rejected
})
it('should reject empty names', async function () {
await expect(this.handler.promises.validateProjectName('')).to.be.rejected
it('should reject empty names', async function (ctx) {
await expect(ctx.handler.promises.validateProjectName('')).to.be.rejected
})
it('should reject names with /s', async function () {
await expect(this.handler.promises.validateProjectName('foo/bar')).to.be
it('should reject names with /s', async function (ctx) {
await expect(ctx.handler.promises.validateProjectName('foo/bar')).to.be
.rejected
})
it('should reject names with \\s', async function () {
await expect(this.handler.promises.validateProjectName('foo\\bar')).to.be
it('should reject names with \\s', async function (ctx) {
await expect(ctx.handler.promises.validateProjectName('foo\\bar')).to.be
.rejected
})
it('should reject long names', async function () {
await expect(this.handler.promises.validateProjectName('a'.repeat(1000)))
it('should reject long names', async function (ctx) {
await expect(ctx.handler.promises.validateProjectName('a'.repeat(1000)))
.to.be.rejected
})
it('should accept normal names', async function () {
await expect(this.handler.promises.validateProjectName('foobar')).to.be
it('should accept normal names', async function (ctx) {
await expect(ctx.handler.promises.validateProjectName('foobar')).to.be
.fulfilled
})
})
describe('generateUniqueName', function () {
// actually testing `ProjectHelper.promises.ensureNameIsUnique()`
beforeEach(function () {
this.longName = 'x'.repeat(this.handler.MAX_PROJECT_NAME_LENGTH - 5)
beforeEach(function (ctx) {
ctx.longName = 'x'.repeat(ctx.handler.MAX_PROJECT_NAME_LENGTH - 5)
const usersProjects = {
owned: [
{ _id: 1, name: 'name' },
@@ -290,116 +316,116 @@ describe('ProjectDetailsHandler', function () {
tokenReadOnly: [
{ _id: 10, name: 'name5' },
{ _id: 11, name: 'name55' },
{ _id: 12, name: this.longName },
{ _id: 12, name: ctx.longName },
],
}
this.ProjectGetter.promises.findAllUsersProjects.resolves(usersProjects)
ctx.ProjectGetter.promises.findAllUsersProjects.resolves(usersProjects)
})
it('should leave a unique name unchanged', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should leave a unique name unchanged', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'unique-name',
['-test-suffix']
)
expect(name).to.equal('unique-name')
})
it('should append a suffix to an existing name', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should append a suffix to an existing name', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'name1',
['-test-suffix']
)
expect(name).to.equal('name1-test-suffix')
})
it('should fallback to a second suffix when needed', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should fallback to a second suffix when needed', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'name1',
['1', '-test-suffix']
)
expect(name).to.equal('name1-test-suffix')
})
it('should truncate the name when append a suffix if the result is too long', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
this.longName,
it('should truncate the name when append a suffix if the result is too long', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
ctx.longName,
['-test-suffix']
)
expect(name).to.equal(
this.longName.substr(0, this.handler.MAX_PROJECT_NAME_LENGTH - 12) +
ctx.longName.substr(0, ctx.handler.MAX_PROJECT_NAME_LENGTH - 12) +
'-test-suffix'
)
})
it('should use a numeric index if no suffix is supplied', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should use a numeric index if no suffix is supplied', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'name1',
[]
)
expect(name).to.equal('name1 (1)')
})
it('should use a numeric index if all suffixes are exhausted', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should use a numeric index if all suffixes are exhausted', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'name',
['1', '11']
)
expect(name).to.equal('name (1)')
})
it('should find the next lowest available numeric index for the base name', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should find the next lowest available numeric index for the base name', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'numeric',
[]
)
expect(name).to.equal('numeric (21)')
})
it('should not find a numeric index lower than the one already present', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should not find a numeric index lower than the one already present', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'numeric (31)',
[]
)
expect(name).to.equal('numeric (41)')
})
it('should handle years in name', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should handle years in name', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'unique-name (2021)',
[]
)
expect(name).to.equal('unique-name (2021)')
})
it('should handle duplicating with year in name', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should handle duplicating with year in name', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'Yearbook (2021)',
[]
)
expect(name).to.equal('Yearbook (2021) (2)')
})
describe('title with that causes invalid regex', function () {
it('should create the project with a suffix when project name exists', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should create the project with a suffix when project name exists', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'Resume (2020',
[]
)
expect(name).to.equal('Resume (2020 (1)')
})
it('should create the project with the provided name', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should create the project with the provided name', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'Yearbook (2021',
[]
)
@@ -409,18 +435,18 @@ describe('ProjectDetailsHandler', function () {
describe('numeric index is already present', function () {
describe('when there is 1 project "x (2)"', function () {
beforeEach(function () {
beforeEach(function (ctx) {
const usersProjects = {
owned: [{ _id: 1, name: 'x (2)' }],
}
this.ProjectGetter.promises.findAllUsersProjects.resolves(
ctx.ProjectGetter.promises.findAllUsersProjects.resolves(
usersProjects
)
})
it('should produce "x (3)" uploading a zip with name "x (2)"', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should produce "x (3)" uploading a zip with name "x (2)"', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'x (2)',
[]
)
@@ -429,21 +455,21 @@ describe('ProjectDetailsHandler', function () {
})
describe('when there are 2 projects "x (2)" and "x (3)"', function () {
beforeEach(function () {
beforeEach(function (ctx) {
const usersProjects = {
owned: [
{ _id: 1, name: 'x (2)' },
{ _id: 2, name: 'x (3)' },
],
}
this.ProjectGetter.promises.findAllUsersProjects.resolves(
ctx.ProjectGetter.promises.findAllUsersProjects.resolves(
usersProjects
)
})
it('should produce "x (4)" when uploading a zip with name "x (2)"', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should produce "x (4)" when uploading a zip with name "x (2)"', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'x (2)',
[]
)
@@ -452,30 +478,30 @@ describe('ProjectDetailsHandler', function () {
})
describe('when there are 2 projects "x (2)" and "x (4)"', function () {
beforeEach(function () {
beforeEach(function (ctx) {
const usersProjects = {
owned: [
{ _id: 1, name: 'x (2)' },
{ _id: 2, name: 'x (4)' },
],
}
this.ProjectGetter.promises.findAllUsersProjects.resolves(
ctx.ProjectGetter.promises.findAllUsersProjects.resolves(
usersProjects
)
})
it('should produce "x (3)" when uploading a zip with name "x (2)"', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should produce "x (3)" when uploading a zip with name "x (2)"', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'x (2)',
[]
)
expect(name).to.equal('x (3)')
})
it('should produce "x (5)" when uploading a zip with name "x (4)"', async function () {
const name = await this.handler.promises.generateUniqueName(
this.user._id,
it('should produce "x (5)" when uploading a zip with name "x (4)"', async function (ctx) {
const name = await ctx.handler.promises.generateUniqueName(
ctx.user._id,
'x (4)',
[]
)
@@ -486,70 +512,70 @@ describe('ProjectDetailsHandler', function () {
})
describe('fixProjectName', function () {
it('should change empty names to Untitled', function () {
expect(this.handler.fixProjectName('')).to.equal('Untitled')
it('should change empty names to Untitled', function (ctx) {
expect(ctx.handler.fixProjectName('')).to.equal('Untitled')
})
it('should replace / with -', function () {
expect(this.handler.fixProjectName('foo/bar')).to.equal('foo-bar')
it('should replace / with -', function (ctx) {
expect(ctx.handler.fixProjectName('foo/bar')).to.equal('foo-bar')
})
it("should replace \\ with ''", function () {
expect(this.handler.fixProjectName('foo \\ bar')).to.equal('foo bar')
it("should replace \\ with ''", function (ctx) {
expect(ctx.handler.fixProjectName('foo \\ bar')).to.equal('foo bar')
})
it('should truncate long names', function () {
expect(this.handler.fixProjectName('a'.repeat(1000))).to.equal(
it('should truncate long names', function (ctx) {
expect(ctx.handler.fixProjectName('a'.repeat(1000))).to.equal(
'a'.repeat(150)
)
})
it('should accept normal names', function () {
expect(this.handler.fixProjectName('foobar')).to.equal('foobar')
it('should accept normal names', function (ctx) {
expect(ctx.handler.fixProjectName('foobar')).to.equal('foobar')
})
it('should trim name after truncation', function () {
expect(this.handler.fixProjectName('a'.repeat(149) + ' a')).to.equal(
it('should trim name after truncation', function (ctx) {
expect(ctx.handler.fixProjectName('a'.repeat(149) + ' a')).to.equal(
'a'.repeat(149)
)
})
})
describe('setPublicAccessLevel', function () {
beforeEach(function () {
this.accessLevel = 'tokenBased'
beforeEach(function (ctx) {
ctx.accessLevel = 'tokenBased'
})
it('should update the project with the new level', async function () {
await this.handler.promises.setPublicAccessLevel(
this.project._id,
this.accessLevel
it('should update the project with the new level', async function (ctx) {
await ctx.handler.promises.setPublicAccessLevel(
ctx.project._id,
ctx.accessLevel
)
expect(this.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: this.project._id },
{ publicAccesLevel: this.accessLevel }
expect(ctx.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: ctx.project._id },
{ publicAccesLevel: ctx.accessLevel }
)
})
it('should not produce an error', async function () {
it('should not produce an error', async function (ctx) {
await expect(
this.handler.promises.setPublicAccessLevel(
this.project._id,
this.accessLevel
ctx.handler.promises.setPublicAccessLevel(
ctx.project._id,
ctx.accessLevel
)
).to.be.fulfilled
})
describe('when update produces an error', function () {
beforeEach(function () {
this.ProjectModelUpdateQuery.exec.rejects(new Error('woops'))
beforeEach(function (ctx) {
ctx.ProjectModelUpdateQuery.exec.rejects(new Error('woops'))
})
it('should produce an error', async function () {
it('should produce an error', async function (ctx) {
await expect(
this.handler.promises.setPublicAccessLevel(
this.project._id,
this.accessLevel
ctx.handler.promises.setPublicAccessLevel(
ctx.project._id,
ctx.accessLevel
)
).to.be.rejected
})
@@ -558,76 +584,76 @@ describe('ProjectDetailsHandler', function () {
describe('ensureTokensArePresent', function () {
describe('when the project has tokens', function () {
beforeEach(function () {
this.project = {
_id: this.project._id,
beforeEach(function (ctx) {
ctx.project = {
_id: ctx.project._id,
tokens: {
readOnly: 'aaa',
readAndWrite: '42bbb',
readAndWritePrefix: '42',
},
}
this.ProjectGetter.promises.getProject.resolves(this.project)
ctx.ProjectGetter.promises.getProject.resolves(ctx.project)
})
it('should get the project', async function () {
await this.handler.promises.ensureTokensArePresent(this.project._id)
expect(this.ProjectGetter.promises.getProject).to.have.been.calledOnce
expect(this.ProjectGetter.promises.getProject).to.have.been.calledWith(
this.project._id,
it('should get the project', async function (ctx) {
await ctx.handler.promises.ensureTokensArePresent(ctx.project._id)
expect(ctx.ProjectGetter.promises.getProject).to.have.been.calledOnce
expect(ctx.ProjectGetter.promises.getProject).to.have.been.calledWith(
ctx.project._id,
{
tokens: 1,
}
)
})
it('should not update the project with new tokens', async function () {
await this.handler.promises.ensureTokensArePresent(this.project._id)
expect(this.ProjectModel.updateOne).not.to.have.been.called
it('should not update the project with new tokens', async function (ctx) {
await ctx.handler.promises.ensureTokensArePresent(ctx.project._id)
expect(ctx.ProjectModel.updateOne).not.to.have.been.called
})
})
describe('when tokens are missing', function () {
beforeEach(function () {
this.project = { _id: this.project._id }
this.ProjectGetter.promises.getProject.resolves(this.project)
this.readOnlyToken = 'abc'
this.readAndWriteToken = '42def'
this.readAndWriteTokenPrefix = '42'
this.TokenGenerator.promises.generateUniqueReadOnlyToken.resolves(
this.readOnlyToken
beforeEach(function (ctx) {
ctx.project = { _id: ctx.project._id }
ctx.ProjectGetter.promises.getProject.resolves(ctx.project)
ctx.readOnlyToken = 'abc'
ctx.readAndWriteToken = '42def'
ctx.readAndWriteTokenPrefix = '42'
ctx.TokenGenerator.promises.generateUniqueReadOnlyToken.resolves(
ctx.readOnlyToken
)
this.TokenGenerator.readAndWriteToken.returns({
token: this.readAndWriteToken,
numericPrefix: this.readAndWriteTokenPrefix,
ctx.TokenGenerator.readAndWriteToken.returns({
token: ctx.readAndWriteToken,
numericPrefix: ctx.readAndWriteTokenPrefix,
})
})
it('should get the project', async function () {
await this.handler.promises.ensureTokensArePresent(this.project._id)
expect(this.ProjectGetter.promises.getProject).to.have.been.calledOnce
expect(this.ProjectGetter.promises.getProject).to.have.been.calledWith(
this.project._id,
it('should get the project', async function (ctx) {
await ctx.handler.promises.ensureTokensArePresent(ctx.project._id)
expect(ctx.ProjectGetter.promises.getProject).to.have.been.calledOnce
expect(ctx.ProjectGetter.promises.getProject).to.have.been.calledWith(
ctx.project._id,
{
tokens: 1,
}
)
})
it('should update the project with new tokens', async function () {
await this.handler.promises.ensureTokensArePresent(this.project._id)
expect(this.TokenGenerator.promises.generateUniqueReadOnlyToken).to.have
it('should update the project with new tokens', async function (ctx) {
await ctx.handler.promises.ensureTokensArePresent(ctx.project._id)
expect(ctx.TokenGenerator.promises.generateUniqueReadOnlyToken).to.have
.been.calledOnce
expect(this.TokenGenerator.readAndWriteToken).to.have.been.calledOnce
expect(this.ProjectModel.updateOne).to.have.been.calledOnce
expect(this.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: this.project._id },
expect(ctx.TokenGenerator.readAndWriteToken).to.have.been.calledOnce
expect(ctx.ProjectModel.updateOne).to.have.been.calledOnce
expect(ctx.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: ctx.project._id },
{
$set: {
tokens: {
readOnly: this.readOnlyToken,
readAndWrite: this.readAndWriteToken,
readAndWritePrefix: this.readAndWriteTokenPrefix,
readOnly: ctx.readOnlyToken,
readAndWrite: ctx.readAndWriteToken,
readAndWritePrefix: ctx.readAndWriteTokenPrefix,
},
},
}
@@ -637,10 +663,10 @@ describe('ProjectDetailsHandler', function () {
})
describe('clearTokens', function () {
it('clears the tokens from the project', async function () {
await this.handler.promises.clearTokens(this.project._id)
expect(this.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: this.project._id },
it('clears the tokens from the project', async function (ctx) {
await ctx.handler.promises.clearTokens(ctx.project._id)
expect(ctx.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: ctx.project._id },
{ $unset: { tokens: 1 }, $set: { publicAccesLevel: 'private' } }
)
})
@@ -1,19 +1,22 @@
const { expect } = require('chai')
const sinon = require('sinon')
import { vi, expect } from 'vitest'
import sinon from 'sinon'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
const modulePath = '../../../../app/src/Features/Project/ProjectEntityHandler'
const SandboxedModule = require('sandboxed-module')
const Errors = require('../../../../app/src/Features/Errors/Errors')
vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
vi.importActual('../../../../app/src/Features/Errors/Errors.js')
)
describe('ProjectEntityHandler', function () {
const projectId = '4eecb1c1bffa66588e0000a1'
const docId = '4eecb1c1bffa66588e0000a2'
beforeEach(function () {
this.TpdsUpdateSender = {
beforeEach(async function (ctx) {
ctx.TpdsUpdateSender = {
addDoc: sinon.stub().callsArg(1),
addFile: sinon.stub().callsArg(1),
}
this.ProjectModel = class Project {
ctx.ProjectModel = class Project {
constructor(options) {
this._id = projectId
this.name = 'project_name_here'
@@ -21,59 +24,77 @@ describe('ProjectEntityHandler', function () {
this.rootFolder = [this.rootFolder]
}
}
this.project = new this.ProjectModel()
ctx.project = new ctx.ProjectModel()
this.ProjectLocator = { findElement: sinon.stub() }
this.DocumentUpdaterHandler = {
ctx.ProjectLocator = { findElement: sinon.stub() }
ctx.DocumentUpdaterHandler = {
updateProjectStructure: sinon.stub().yields(),
}
this.callback = sinon.stub()
ctx.callback = sinon.stub()
this.ProjectEntityHandler = SandboxedModule.require(modulePath, {
requires: {
'../Docstore/DocstoreManager': (this.DocstoreManager = {
promises: {},
}),
'../../Features/DocumentUpdater/DocumentUpdaterHandler':
this.DocumentUpdaterHandler,
'../../models/Project': {
Project: this.ProjectModel,
},
'./ProjectLocator': this.ProjectLocator,
'./ProjectGetter': (this.ProjectGetter = { promises: {} }),
'../ThirdPartyDataStore/TpdsUpdateSender': this.TpdsUpdateSender,
},
})
vi.doMock('../../../../app/src/Features/Docstore/DocstoreManager', () => ({
default: (ctx.DocstoreManager = {
promises: {},
}),
}))
vi.doMock(
'../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler',
() => ({
default: ctx.DocumentUpdaterHandler,
})
)
vi.doMock('../../../../app/src/models/Project', () => ({
Project: ctx.ProjectModel,
}))
vi.doMock('../../../../app/src/Features/Project/ProjectLocator', () => ({
default: ctx.ProjectLocator,
}))
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
default: (ctx.ProjectGetter = { promises: {} }),
}))
vi.doMock(
'../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender',
() => ({
default: ctx.TpdsUpdateSender,
})
)
ctx.ProjectEntityHandler = (await import(modulePath)).default
})
describe('getting folders, docs and files', function () {
beforeEach(function () {
this.project.rootFolder = [
beforeEach(function (ctx) {
ctx.project.rootFolder = [
{
docs: [
(this.doc1 = {
(ctx.doc1 = {
name: 'doc1',
_id: 'doc1_id',
}),
],
fileRefs: [
(this.file1 = {
(ctx.file1 = {
rev: 1,
_id: 'file1_id',
name: 'file1',
}),
],
folders: [
(this.folder1 = {
(ctx.folder1 = {
name: 'folder1',
docs: [
(this.doc2 = {
(ctx.doc2 = {
name: 'doc2',
_id: 'doc2_id',
}),
],
fileRefs: [
(this.file2 = {
(ctx.file2 = {
rev: 2,
name: 'file2',
_id: 'file2_id',
@@ -84,54 +105,54 @@ describe('ProjectEntityHandler', function () {
],
},
]
this.ProjectGetter.promises.getProjectWithoutDocLines = sinon
ctx.ProjectGetter.promises.getProjectWithoutDocLines = sinon
.stub()
.resolves(this.project)
.resolves(ctx.project)
})
describe('getAllDocs', function () {
let fetchedDocs
beforeEach(async function () {
this.docs = [
beforeEach(async function (ctx) {
ctx.docs = [
{
_id: this.doc1._id,
lines: (this.lines1 = ['one']),
rev: (this.rev1 = 1),
_id: ctx.doc1._id,
lines: (ctx.lines1 = ['one']),
rev: (ctx.rev1 = 1),
},
{
_id: this.doc2._id,
lines: (this.lines2 = ['two']),
rev: (this.rev2 = 2),
_id: ctx.doc2._id,
lines: (ctx.lines2 = ['two']),
rev: (ctx.rev2 = 2),
},
]
this.DocstoreManager.promises.getAllDocs = sinon
ctx.DocstoreManager.promises.getAllDocs = sinon
.stub()
.resolves(this.docs)
.resolves(ctx.docs)
fetchedDocs =
await this.ProjectEntityHandler.promises.getAllDocs(projectId)
await ctx.ProjectEntityHandler.promises.getAllDocs(projectId)
})
it('should get the doc lines and rev from the docstore', function () {
this.DocstoreManager.promises.getAllDocs
it('should get the doc lines and rev from the docstore', function (ctx) {
ctx.DocstoreManager.promises.getAllDocs
.calledWith(projectId)
.should.equal(true)
})
it('should call the callback with the docs with the lines and rev included', function () {
it('should call the callback with the docs with the lines and rev included', function (ctx) {
expect(fetchedDocs).to.deep.equal({
'/doc1': {
_id: this.doc1._id,
lines: this.lines1,
name: this.doc1.name,
rev: this.rev1,
folder: this.project.rootFolder[0],
_id: ctx.doc1._id,
lines: ctx.lines1,
name: ctx.doc1.name,
rev: ctx.rev1,
folder: ctx.project.rootFolder[0],
},
'/folder1/doc2': {
_id: this.doc2._id,
lines: this.lines2,
name: this.doc2.name,
rev: this.rev2,
folder: this.folder1,
_id: ctx.doc2._id,
lines: ctx.lines2,
name: ctx.doc2.name,
rev: ctx.rev2,
folder: ctx.folder1,
},
})
})
@@ -139,85 +160,85 @@ describe('ProjectEntityHandler', function () {
describe('getAllFiles', function () {
let allFiles
beforeEach(async function () {
this.callback = sinon.stub()
allFiles = await this.ProjectEntityHandler.promises.getAllFiles(
beforeEach(async function (ctx) {
ctx.callback = sinon.stub()
allFiles = await ctx.ProjectEntityHandler.promises.getAllFiles(
projectId,
this.callback
ctx.callback
)
})
it('should call the callback with the files', function () {
it('should call the callback with the files', function (ctx) {
expect(allFiles).to.deep.equal({
'/file1': { ...this.file1, folder: this.project.rootFolder[0] },
'/folder1/file2': { ...this.file2, folder: this.folder1 },
'/file1': { ...ctx.file1, folder: ctx.project.rootFolder[0] },
'/folder1/file2': { ...ctx.file2, folder: ctx.folder1 },
})
})
})
describe('getAllDocPathsFromProject', function () {
beforeEach(function () {
this.docs = [
beforeEach(function (ctx) {
ctx.docs = [
{
_id: this.doc1._id,
lines: (this.lines1 = ['one']),
rev: (this.rev1 = 1),
_id: ctx.doc1._id,
lines: (ctx.lines1 = ['one']),
rev: (ctx.rev1 = 1),
},
{
_id: this.doc2._id,
lines: (this.lines2 = ['two']),
rev: (this.rev2 = 2),
_id: ctx.doc2._id,
lines: (ctx.lines2 = ['two']),
rev: (ctx.rev2 = 2),
},
]
})
it('should call the callback with the path for each docId', function () {
it('should call the callback with the path for each docId', function (ctx) {
const expected = {
[this.doc1._id]: `/${this.doc1.name}`,
[this.doc2._id]: `/folder1/${this.doc2.name}`,
[ctx.doc1._id]: `/${ctx.doc1.name}`,
[ctx.doc2._id]: `/folder1/${ctx.doc2.name}`,
}
expect(
this.ProjectEntityHandler.getAllDocPathsFromProject(
this.project,
this.callback
ctx.ProjectEntityHandler.getAllDocPathsFromProject(
ctx.project,
ctx.callback
)
).to.deep.equal(expected)
})
})
describe('getDocPathByProjectIdAndDocId', function () {
it('should call the callback with the path for an existing doc id at the root level', async function () {
it('should call the callback with the path for an existing doc id at the root level', async function (ctx) {
const path =
await this.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
await ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
projectId,
this.doc1._id
ctx.doc1._id
)
expect(path).to.deep.equal(`/${this.doc1.name}`)
expect(path).to.deep.equal(`/${ctx.doc1.name}`)
})
it('should call the callback with the path for an existing doc id nested within a folder', async function () {
it('should call the callback with the path for an existing doc id nested within a folder', async function (ctx) {
const path =
await this.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
await ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
projectId,
this.doc2._id
ctx.doc2._id
)
expect(path).to.deep.equal(`/folder1/${this.doc2.name}`)
expect(path).to.deep.equal(`/folder1/${ctx.doc2.name}`)
})
it('should call the callback with a NotFoundError for a non-existing doc', async function () {
it('should call the callback with a NotFoundError for a non-existing doc', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
projectId,
'non-existing-id'
)
).to.be.rejectedWith(Errors.NotFoundError)
})
it('should call the callback with a NotFoundError for an existing file', async function () {
it('should call the callback with a NotFoundError for an existing file', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
projectId,
this.file1._id
ctx.file1._id
)
).to.be.rejectedWith(Errors.NotFoundError)
})
@@ -225,66 +246,66 @@ describe('ProjectEntityHandler', function () {
describe('_getAllFolders', async function () {
let folders
beforeEach(async function () {
this.callback = sinon.stub()
beforeEach(async function (ctx) {
ctx.callback = sinon.stub()
folders =
await this.ProjectEntityHandler.promises._getAllFolders(projectId)
await ctx.ProjectEntityHandler.promises._getAllFolders(projectId)
})
it('should get the project without the docs lines', function () {
this.ProjectGetter.promises.getProjectWithoutDocLines
it('should get the project without the docs lines', function (ctx) {
ctx.ProjectGetter.promises.getProjectWithoutDocLines
.calledWith(projectId)
.should.equal(true)
})
it('should call the callback with the folders', function () {
it('should call the callback with the folders', function (ctx) {
expect(folders).to.deep.equal([
{ path: '/', folder: this.project.rootFolder[0] },
{ path: '/folder1', folder: this.folder1 },
{ path: '/', folder: ctx.project.rootFolder[0] },
{ path: '/folder1', folder: ctx.folder1 },
])
})
})
describe('_getAllFoldersFromProject', function () {
it('should return the folders', function () {
it('should return the folders', function (ctx) {
expect(
this.ProjectEntityHandler._getAllFoldersFromProject(this.project)
ctx.ProjectEntityHandler._getAllFoldersFromProject(ctx.project)
).to.deep.equal([
{ path: '/', folder: this.project.rootFolder[0] },
{ path: '/folder1', folder: this.folder1 },
{ path: '/', folder: ctx.project.rootFolder[0] },
{ path: '/folder1', folder: ctx.folder1 },
])
})
})
})
describe('with an invalid file tree', function () {
beforeEach(function () {
this.project.rootFolder = [
beforeEach(function (ctx) {
ctx.project.rootFolder = [
{
docs: [
(this.doc1 = {
(ctx.doc1 = {
name: null, // invalid doc name
_id: 'doc1_id',
}),
],
fileRefs: [
(this.file1 = {
(ctx.file1 = {
rev: 1,
_id: 'file1_id',
name: null, // invalid file name
}),
],
folders: [
(this.folder1 = {
(ctx.folder1 = {
name: null, // invalid folder name
docs: [
(this.doc2 = {
(ctx.doc2 = {
name: 'doc2',
_id: 'doc2_id',
}),
],
fileRefs: [
(this.file2 = {
(ctx.file2 = {
rev: 2,
name: 'file2',
_id: 'file2_id',
@@ -296,107 +317,107 @@ describe('ProjectEntityHandler', function () {
],
},
]
this.ProjectGetter.promises.getProjectWithoutDocLines = sinon
ctx.ProjectGetter.promises.getProjectWithoutDocLines = sinon
.stub()
.resolves(this.project)
.resolves(ctx.project)
})
describe('getAllDocs', function () {
beforeEach(async function () {
this.docs = [
beforeEach(async function (ctx) {
ctx.docs = [
{
_id: this.doc1._id,
lines: (this.lines1 = ['one']),
rev: (this.rev1 = 1),
_id: ctx.doc1._id,
lines: (ctx.lines1 = ['one']),
rev: (ctx.rev1 = 1),
},
{
_id: this.doc2._id,
lines: (this.lines2 = ['two']),
rev: (this.rev2 = 2),
_id: ctx.doc2._id,
lines: (ctx.lines2 = ['two']),
rev: (ctx.rev2 = 2),
},
]
this.DocstoreManager.promises.getAllDocs = sinon
ctx.DocstoreManager.promises.getAllDocs = sinon
.stub()
.resolves(this.docs)
.resolves(ctx.docs)
})
it('should call the callback with an error', async function () {
await expect(this.ProjectEntityHandler.promises.getAllDocs(projectId))
.to.be.rejected
it('should call the callback with an error', async function (ctx) {
await expect(ctx.ProjectEntityHandler.promises.getAllDocs(projectId)).to
.be.rejected
})
})
describe('getAllFiles', function () {
it('should call the callback with and error', async function () {
await expect(this.ProjectEntityHandler.promises.getAllFiles(projectId))
it('should call the callback with and error', async function (ctx) {
await expect(ctx.ProjectEntityHandler.promises.getAllFiles(projectId))
.to.be.rejected
})
})
describe('getDocPathByProjectIdAndDocId', function () {
it('should call the callback with an error for an existing doc id at the root level', async function () {
it('should call the callback with an error for an existing doc id at the root level', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
projectId,
this.doc1._id
ctx.doc1._id
)
).to.be.rejectedWith(Error)
})
it('should call the callback with an error for an existing doc id nested within a folder', async function () {
it('should call the callback with an error for an existing doc id nested within a folder', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
projectId,
this.doc2._id
ctx.doc2._id
)
).to.be.rejectedWith(Error)
})
it('should call the callback with an error for a non-existing doc', async function () {
it('should call the callback with an error for a non-existing doc', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
projectId,
'non-existing-id'
)
).to.be.rejectedWith(Error)
})
it('should call the callback with an error for an existing file', async function () {
it('should call the callback with an error for an existing file', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
projectId,
this.file1._id
ctx.file1._id
)
).to.be.rejectedWith(Error)
})
})
describe('_getAllFolders', function () {
it('should call the callback with an error', async function () {
it('should call the callback with an error', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises._getAllFolders(projectId)
ctx.ProjectEntityHandler.promises._getAllFolders(projectId)
).to.be.rejected
})
})
describe('getAllEntities', function () {
beforeEach(function () {
this.ProjectGetter.promises.getProject = sinon
beforeEach(function (ctx) {
ctx.ProjectGetter.promises.getProject = sinon
.stub()
.resolves(this.project)
.resolves(ctx.project)
})
it('should call the callback with an error', async function () {
it('should call the callback with an error', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises.getAllEntities(projectId)
ctx.ProjectEntityHandler.promises.getAllEntities(projectId)
).to.be.rejected
})
})
describe('getAllDocPathsFromProjectById', function () {
it('should call the callback with an error', async function () {
it('should call the callback with an error', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises.getAllDocPathsFromProjectById(
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById(
projectId
)
).to.be.rejected
@@ -404,11 +425,11 @@ describe('ProjectEntityHandler', function () {
})
describe('getDocPathFromProjectByDocId', function () {
it('should call the callback with an error', async function () {
it('should call the callback with an error', async function (ctx) {
await expect(
this.ProjectEntityHandler.promises.getDocPathFromProjectByDocId(
ctx.ProjectEntityHandler.promises.getDocPathFromProjectByDocId(
projectId,
this.doc1._id
ctx.doc1._id
)
).to.be.rejected
})
@@ -416,26 +437,26 @@ describe('ProjectEntityHandler', function () {
})
describe('getDoc', function () {
beforeEach(function () {
this.lines = ['mock', 'doc', 'lines']
this.rev = 5
this.version = 42
this.ranges = { mock: 'ranges' }
this.callback = sinon.stub()
this.DocstoreManager.promises.getDoc = sinon.stub().resolves({
lines: this.lines,
rev: this.rev,
version: this.version,
ranges: this.ranges,
beforeEach(function (ctx) {
ctx.lines = ['mock', 'doc', 'lines']
ctx.rev = 5
ctx.version = 42
ctx.ranges = { mock: 'ranges' }
ctx.callback = sinon.stub()
ctx.DocstoreManager.promises.getDoc = sinon.stub().resolves({
lines: ctx.lines,
rev: ctx.rev,
version: ctx.version,
ranges: ctx.ranges,
})
})
it('should call the callback with the lines, version and rev', async function () {
const doc = await this.ProjectEntityHandler.promises.getDoc(
it('should call the callback with the lines, version and rev', async function (ctx) {
const doc = await ctx.ProjectEntityHandler.promises.getDoc(
projectId,
docId
)
this.DocstoreManager.promises.getDoc
ctx.DocstoreManager.promises.getDoc
.calledWith(projectId, docId)
.should.equal(true)
expect(doc).to.exist
@@ -445,32 +466,32 @@ describe('ProjectEntityHandler', function () {
describe('promises.getDoc', function () {
let result
beforeEach(async function () {
this.lines = ['mock', 'doc', 'lines']
this.rev = 5
this.version = 42
this.ranges = { mock: 'ranges' }
beforeEach(async function (ctx) {
ctx.lines = ['mock', 'doc', 'lines']
ctx.rev = 5
ctx.version = 42
ctx.ranges = { mock: 'ranges' }
this.DocstoreManager.promises.getDoc = sinon.stub().resolves({
lines: this.lines,
rev: this.rev,
version: this.version,
ranges: this.ranges,
ctx.DocstoreManager.promises.getDoc = sinon.stub().resolves({
lines: ctx.lines,
rev: ctx.rev,
version: ctx.version,
ranges: ctx.ranges,
})
result = await this.ProjectEntityHandler.promises.getDoc(projectId, docId)
result = await ctx.ProjectEntityHandler.promises.getDoc(projectId, docId)
})
it('should call the docstore', function () {
this.DocstoreManager.promises.getDoc
it('should call the docstore', function (ctx) {
ctx.DocstoreManager.promises.getDoc
.calledWith(projectId, docId)
.should.equal(true)
})
it('should return the lines, rev, version and ranges', function () {
expect(result.lines).to.equal(this.lines)
expect(result.rev).to.equal(this.rev)
expect(result.version).to.equal(this.version)
expect(result.ranges).to.equal(this.ranges)
it('should return the lines, rev, version and ranges', function (ctx) {
expect(result.lines).to.equal(ctx.lines)
expect(result.rev).to.equal(ctx.rev)
expect(result.version).to.equal(ctx.version)
expect(result.ranges).to.equal(ctx.ranges)
})
})
})
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,30 +1,31 @@
const sinon = require('sinon')
const { expect } = require('chai')
const modulePath = '../../../../app/src/Features/Project/ProjectGetter.js'
const SandboxedModule = require('sandboxed-module')
const { ObjectId } = require('mongodb-legacy')
import { vi, expect } from 'vitest'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
const modulePath = '../../../../app/src/Features/Project/ProjectGetter.mjs'
const { ObjectId } = mongodb
describe('ProjectGetter', function () {
beforeEach(function () {
this.project = { _id: new ObjectId() }
this.projectIdStr = this.project._id.toString()
this.deletedProject = { deleterData: { wombat: 'potato' } }
this.userId = new ObjectId()
beforeEach(async function (ctx) {
ctx.project = { _id: new ObjectId() }
ctx.projectIdStr = ctx.project._id.toString()
ctx.deletedProject = { deleterData: { wombat: 'potato' } }
ctx.userId = new ObjectId()
this.DeletedProject = {
ctx.DeletedProject = {
find: sinon.stub().returns({
exec: sinon.stub().resolves([this.deletedProject]),
exec: sinon.stub().resolves([ctx.deletedProject]),
}),
}
this.Project = {
ctx.Project = {
find: sinon.stub().returns({
exec: sinon.stub().resolves(),
}),
findOne: sinon.stub().returns({
exec: sinon.stub().resolves(this.project),
exec: sinon.stub().resolves(ctx.project),
}),
}
this.CollaboratorsGetter = {
ctx.CollaboratorsGetter = {
promises: {
getProjectsUserIsMemberOf: sinon.stub().resolves({
readAndWrite: [],
@@ -34,58 +35,76 @@ describe('ProjectGetter', function () {
}),
},
}
this.LockManager = {
ctx.LockManager = {
promises: {
runWithLock: sinon
.stub()
.callsFake((namespace, id, runner) => runner()),
},
}
this.db = {
ctx.db = {
projects: {
findOne: sinon.stub().resolves(this.project),
findOne: sinon.stub().resolves(ctx.project),
},
users: {},
}
this.ProjectEntityMongoUpdateHandler = {
ctx.ProjectEntityMongoUpdateHandler = {
lockKey: sinon.stub().returnsArg(0),
}
this.ProjectGetter = SandboxedModule.require(modulePath, {
requires: {
'../../infrastructure/mongodb': { db: this.db, ObjectId },
'../../models/Project': {
Project: this.Project,
},
'../../models/DeletedProject': {
DeletedProject: this.DeletedProject,
},
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
'../../infrastructure/LockManager': this.LockManager,
'./ProjectEntityMongoUpdateHandler':
this.ProjectEntityMongoUpdateHandler,
},
})
vi.doMock('../../../../app/src/infrastructure/mongodb', () => ({
db: ctx.db,
ObjectId,
}))
vi.doMock('../../../../app/src/models/Project', () => ({
Project: ctx.Project,
}))
vi.doMock('../../../../app/src/models/DeletedProject', () => ({
DeletedProject: ctx.DeletedProject,
}))
vi.doMock(
'../../../../app/src/Features/Collaborators/CollaboratorsGetter',
() => ({
default: ctx.CollaboratorsGetter,
})
)
vi.doMock('../../../../app/src/infrastructure/LockManager', () => ({
default: ctx.LockManager,
}))
vi.doMock(
'../../../../app/src/Features/Project/ProjectEntityMongoUpdateHandler',
() => ({
default: ctx.ProjectEntityMongoUpdateHandler,
})
)
ctx.ProjectGetter = (await import(modulePath)).default
})
describe('getProjectWithoutDocLines', function () {
beforeEach(function () {
this.ProjectGetter.promises.getProject = sinon.stub().resolves()
beforeEach(function (ctx) {
ctx.ProjectGetter.promises.getProject = sinon.stub().resolves()
})
describe('passing an id', function () {
beforeEach(async function () {
await this.ProjectGetter.promises.getProjectWithoutDocLines(
this.project._id
beforeEach(async function (ctx) {
await ctx.ProjectGetter.promises.getProjectWithoutDocLines(
ctx.project._id
)
})
it('should call find with the project id', function () {
this.ProjectGetter.promises.getProject
.calledWith(this.project._id)
it('should call find with the project id', function (ctx) {
ctx.ProjectGetter.promises.getProject
.calledWith(ctx.project._id)
.should.equal(true)
})
it('should exclude the doc lines', function () {
it('should exclude the doc lines', function (ctx) {
const excludes = {
'rootFolder.docs.lines': 0,
'rootFolder.folders.docs.lines': 0,
@@ -97,32 +116,32 @@ describe('ProjectGetter', function () {
'rootFolder.folders.folders.folders.folders.folders.folders.folders.docs.lines': 0,
}
this.ProjectGetter.promises.getProject
.calledWith(this.project._id, excludes)
ctx.ProjectGetter.promises.getProject
.calledWith(ctx.project._id, excludes)
.should.equal(true)
})
})
})
describe('getProjectWithOnlyFolders', function () {
beforeEach(function () {
this.ProjectGetter.promises.getProject = sinon.stub().resolves()
beforeEach(function (ctx) {
ctx.ProjectGetter.promises.getProject = sinon.stub().resolves()
})
describe('passing an id', function () {
beforeEach(async function () {
await this.ProjectGetter.promises.getProjectWithOnlyFolders(
this.project._id
beforeEach(async function (ctx) {
await ctx.ProjectGetter.promises.getProjectWithOnlyFolders(
ctx.project._id
)
})
it('should call find with the project id', function () {
this.ProjectGetter.promises.getProject
.calledWith(this.project._id)
it('should call find with the project id', function (ctx) {
ctx.ProjectGetter.promises.getProject
.calledWith(ctx.project._id)
.should.equal(true)
})
it('should exclude the docs and files lines', function () {
it('should exclude the docs and files lines', function (ctx) {
const excludes = {
'rootFolder.docs': 0,
'rootFolder.fileRefs': 0,
@@ -141,8 +160,8 @@ describe('ProjectGetter', function () {
'rootFolder.folders.folders.folders.folders.folders.folders.folders.docs': 0,
'rootFolder.folders.folders.folders.folders.folders.folders.folders.fileRefs': 0,
}
this.ProjectGetter.promises.getProject
.calledWith(this.project._id, excludes)
ctx.ProjectGetter.promises.getProject
.calledWith(ctx.project._id, excludes)
.should.equal(true)
})
})
@@ -151,58 +170,58 @@ describe('ProjectGetter', function () {
describe('getProject', function () {
describe('without projection', function () {
describe('with project id', function () {
beforeEach(async function () {
await this.ProjectGetter.promises.getProject(this.projectIdStr)
beforeEach(async function (ctx) {
await ctx.ProjectGetter.promises.getProject(ctx.projectIdStr)
})
it('should call findOne with the project id', function () {
expect(this.db.projects.findOne.callCount).to.equal(1)
it('should call findOne with the project id', function (ctx) {
expect(ctx.db.projects.findOne.callCount).to.equal(1)
expect(
this.db.projects.findOne.lastCall.args[0]._id.toString()
).to.equal(this.projectIdStr)
ctx.db.projects.findOne.lastCall.args[0]._id.toString()
).to.equal(ctx.projectIdStr)
})
})
describe('without project id', function () {
it('should be rejected', function () {
it('should be rejected', function (ctx) {
expect(
this.ProjectGetter.promises.getProject(null)
ctx.ProjectGetter.promises.getProject(null)
).to.be.rejectedWith('no project id provided')
expect(this.db.projects.findOne.callCount).to.equal(0)
expect(ctx.db.projects.findOne.callCount).to.equal(0)
})
})
})
describe('with projection', function () {
beforeEach(function () {
this.projection = { _id: 1 }
beforeEach(function (ctx) {
ctx.projection = { _id: 1 }
})
describe('with project id', function () {
beforeEach(async function () {
await this.ProjectGetter.promises.getProject(
this.projectIdStr,
this.projection
beforeEach(async function (ctx) {
await ctx.ProjectGetter.promises.getProject(
ctx.projectIdStr,
ctx.projection
)
})
it('should call findOne with the project id', function () {
expect(this.db.projects.findOne.callCount).to.equal(1)
it('should call findOne with the project id', function (ctx) {
expect(ctx.db.projects.findOne.callCount).to.equal(1)
expect(
this.db.projects.findOne.lastCall.args[0]._id.toString()
).to.equal(this.projectIdStr)
expect(this.db.projects.findOne.lastCall.args[1]).to.deep.equal({
projection: this.projection,
ctx.db.projects.findOne.lastCall.args[0]._id.toString()
).to.equal(ctx.projectIdStr)
expect(ctx.db.projects.findOne.lastCall.args[1]).to.deep.equal({
projection: ctx.projection,
})
})
})
describe('without project id', function () {
it('should be rejected', function () {
it('should be rejected', function (ctx) {
expect(
this.ProjectGetter.promises.getProject(null)
ctx.ProjectGetter.promises.getProject(null)
).to.be.rejectedWith('no project id provided')
expect(this.db.projects.findOne.callCount).to.equal(0)
expect(ctx.db.projects.findOne.callCount).to.equal(0)
})
})
})
@@ -211,155 +230,151 @@ describe('ProjectGetter', function () {
describe('getProjectWithoutLock', function () {
describe('without projection', function () {
describe('with project id', function () {
beforeEach(async function () {
await this.ProjectGetter.promises.getProjectWithoutLock(
this.projectIdStr
beforeEach(async function (ctx) {
await ctx.ProjectGetter.promises.getProjectWithoutLock(
ctx.projectIdStr
)
})
it('should call findOne with the project id', function () {
expect(this.db.projects.findOne.callCount).to.equal(1)
it('should call findOne with the project id', function (ctx) {
expect(ctx.db.projects.findOne.callCount).to.equal(1)
expect(
this.db.projects.findOne.lastCall.args[0]._id.toString()
).to.equal(this.projectIdStr)
ctx.db.projects.findOne.lastCall.args[0]._id.toString()
).to.equal(ctx.projectIdStr)
})
})
describe('without project id', function () {
it('should be rejected', function () {
it('should be rejected', function (ctx) {
expect(
this.ProjectGetter.promises.getProjectWithoutLock(null)
ctx.ProjectGetter.promises.getProjectWithoutLock(null)
).to.be.rejectedWith('no project id provided')
expect(this.db.projects.findOne.callCount).to.equal(0)
expect(ctx.db.projects.findOne.callCount).to.equal(0)
})
})
})
describe('with projection', function () {
beforeEach(function () {
this.projection = { _id: 1 }
beforeEach(function (ctx) {
ctx.projection = { _id: 1 }
})
describe('with project id', function () {
beforeEach(async function () {
await this.ProjectGetter.promises.getProjectWithoutLock(
this.project._id,
this.projection
beforeEach(async function (ctx) {
await ctx.ProjectGetter.promises.getProjectWithoutLock(
ctx.project._id,
ctx.projection
)
})
it('should call findOne with the project id', function () {
expect(this.db.projects.findOne.callCount).to.equal(1)
it('should call findOne with the project id', function (ctx) {
expect(ctx.db.projects.findOne.callCount).to.equal(1)
expect(
this.db.projects.findOne.lastCall.args[0]._id.toString()
).to.equal(this.projectIdStr)
expect(this.db.projects.findOne.lastCall.args[1]).to.deep.equal({
projection: this.projection,
ctx.db.projects.findOne.lastCall.args[0]._id.toString()
).to.equal(ctx.projectIdStr)
expect(ctx.db.projects.findOne.lastCall.args[1]).to.deep.equal({
projection: ctx.projection,
})
})
})
describe('without project id', function () {
it('should be rejected', function () {
it('should be rejected', function (ctx) {
expect(
this.ProjectGetter.promises.getProjectWithoutLock(null)
ctx.ProjectGetter.promises.getProjectWithoutLock(null)
).to.be.rejectedWith('no project id provided')
expect(this.db.projects.findOne.callCount).to.equal(0)
expect(ctx.db.projects.findOne.callCount).to.equal(0)
})
})
})
})
describe('findAllUsersProjects', function () {
beforeEach(function () {
this.fields = { mock: 'fields' }
this.projectOwned = { _id: 'mock-owned-projects' }
this.projectRW = { _id: 'mock-rw-projects' }
this.projectReview = { _id: 'mock-review-projects' }
this.projectRO = { _id: 'mock-ro-projects' }
this.projectTokenRW = { _id: 'mock-token-rw-projects' }
this.projectTokenRO = { _id: 'mock-token-ro-projects' }
this.Project.find
.withArgs({ owner_ref: this.userId }, this.fields)
.returns({ exec: sinon.stub().resolves([this.projectOwned]) })
beforeEach(function (ctx) {
ctx.fields = { mock: 'fields' }
ctx.projectOwned = { _id: 'mock-owned-projects' }
ctx.projectRW = { _id: 'mock-rw-projects' }
ctx.projectReview = { _id: 'mock-review-projects' }
ctx.projectRO = { _id: 'mock-ro-projects' }
ctx.projectTokenRW = { _id: 'mock-token-rw-projects' }
ctx.projectTokenRO = { _id: 'mock-token-ro-projects' }
ctx.Project.find
.withArgs({ owner_ref: ctx.userId }, ctx.fields)
.returns({ exec: sinon.stub().resolves([ctx.projectOwned]) })
})
it('should return a promise with all the projects', async function () {
this.CollaboratorsGetter.promises.getProjectsUserIsMemberOf.resolves({
readAndWrite: [this.projectRW],
readOnly: [this.projectRO],
tokenReadAndWrite: [this.projectTokenRW],
tokenReadOnly: [this.projectTokenRO],
review: [this.projectReview],
it('should return a promise with all the projects', async function (ctx) {
ctx.CollaboratorsGetter.promises.getProjectsUserIsMemberOf.resolves({
readAndWrite: [ctx.projectRW],
readOnly: [ctx.projectRO],
tokenReadAndWrite: [ctx.projectTokenRW],
tokenReadOnly: [ctx.projectTokenRO],
review: [ctx.projectReview],
})
const projects = await this.ProjectGetter.promises.findAllUsersProjects(
this.userId,
this.fields
const projects = await ctx.ProjectGetter.promises.findAllUsersProjects(
ctx.userId,
ctx.fields
)
expect(projects).to.deep.equal({
owned: [this.projectOwned],
readAndWrite: [this.projectRW],
readOnly: [this.projectRO],
tokenReadAndWrite: [this.projectTokenRW],
tokenReadOnly: [this.projectTokenRO],
review: [this.projectReview],
owned: [ctx.projectOwned],
readAndWrite: [ctx.projectRW],
readOnly: [ctx.projectRO],
tokenReadAndWrite: [ctx.projectTokenRW],
tokenReadOnly: [ctx.projectTokenRO],
review: [ctx.projectReview],
})
})
it('should remove duplicate projects', async function () {
this.CollaboratorsGetter.promises.getProjectsUserIsMemberOf.resolves({
readAndWrite: [this.projectRW, this.projectOwned],
readOnly: [this.projectRO, this.projectRW],
tokenReadAndWrite: [this.projectTokenRW, this.projectRO],
tokenReadOnly: [
this.projectTokenRW,
this.projectTokenRO,
this.projectRO,
],
review: [this.projectReview],
it('should remove duplicate projects', async function (ctx) {
ctx.CollaboratorsGetter.promises.getProjectsUserIsMemberOf.resolves({
readAndWrite: [ctx.projectRW, ctx.projectOwned],
readOnly: [ctx.projectRO, ctx.projectRW],
tokenReadAndWrite: [ctx.projectTokenRW, ctx.projectRO],
tokenReadOnly: [ctx.projectTokenRW, ctx.projectTokenRO, ctx.projectRO],
review: [ctx.projectReview],
})
const projects = await this.ProjectGetter.promises.findAllUsersProjects(
this.userId,
this.fields
const projects = await ctx.ProjectGetter.promises.findAllUsersProjects(
ctx.userId,
ctx.fields
)
expect(projects).to.deep.equal({
owned: [this.projectOwned],
readAndWrite: [this.projectRW],
readOnly: [this.projectRO],
tokenReadAndWrite: [this.projectTokenRW],
tokenReadOnly: [this.projectTokenRO],
review: [this.projectReview],
owned: [ctx.projectOwned],
readAndWrite: [ctx.projectRW],
readOnly: [ctx.projectRO],
tokenReadAndWrite: [ctx.projectTokenRW],
tokenReadOnly: [ctx.projectTokenRO],
review: [ctx.projectReview],
})
})
})
describe('getProjectIdByReadAndWriteToken', function () {
describe('when project find returns project', function () {
this.beforeEach(async function () {
this.projectIdFound =
await this.ProjectGetter.promises.getProjectIdByReadAndWriteToken(
beforeEach(async function (ctx) {
ctx.projectIdFound =
await ctx.ProjectGetter.promises.getProjectIdByReadAndWriteToken(
'token'
)
})
it('should find project with token', function () {
this.Project.findOne
it('should find project with token', function (ctx) {
ctx.Project.findOne
.calledWithMatch({ 'tokens.readAndWrite': 'token' })
.should.equal(true)
})
it('should return the project id', function () {
expect(this.projectIdFound).to.equal(this.project._id)
it('should return the project id', function (ctx) {
expect(ctx.projectIdFound).to.equal(ctx.project._id)
})
})
describe('when project not found', function () {
it('should return undefined', async function () {
this.Project.findOne.returns({ exec: sinon.stub().resolves(null) })
it('should return undefined', async function (ctx) {
ctx.Project.findOne.returns({ exec: sinon.stub().resolves(null) })
const projectId =
await this.ProjectGetter.promises.getProjectIdByReadAndWriteToken(
await ctx.ProjectGetter.promises.getProjectIdByReadAndWriteToken(
'token'
)
@@ -368,86 +383,79 @@ describe('ProjectGetter', function () {
})
describe('when project find returns error', function () {
this.beforeEach(async function () {
this.Project.findOne.returns({ exec: sinon.stub().rejects() })
beforeEach(async function (ctx) {
ctx.Project.findOne.returns({ exec: sinon.stub().rejects() })
})
it('should be rejected', function () {
it('should be rejected', function (ctx) {
expect(
this.ProjectGetter.promises.getProjectIdByReadAndWriteToken('token')
ctx.ProjectGetter.promises.getProjectIdByReadAndWriteToken('token')
).to.be.rejected
})
})
})
describe('findUsersProjectsByName', function () {
it('should perform a case-insensitive search', async function () {
this.project1 = { _id: 1, name: 'find me!' }
this.project2 = { _id: 2, name: 'not me!' }
this.project3 = { _id: 3, name: 'FIND ME!' }
this.project4 = { _id: 4, name: 'Find Me!' }
this.Project.find.withArgs({ owner_ref: this.userId }).returns({
it('should perform a case-insensitive search', async function (ctx) {
ctx.project1 = { _id: 1, name: 'find me!' }
ctx.project2 = { _id: 2, name: 'not me!' }
ctx.project3 = { _id: 3, name: 'FIND ME!' }
ctx.project4 = { _id: 4, name: 'Find Me!' }
ctx.Project.find.withArgs({ owner_ref: ctx.userId }).returns({
exec: sinon
.stub()
.resolves([
this.project1,
this.project2,
this.project3,
this.project4,
]),
.resolves([ctx.project1, ctx.project2, ctx.project3, ctx.project4]),
})
const projects =
await this.ProjectGetter.promises.findUsersProjectsByName(
this.userId,
this.project1.name
)
const projects = await ctx.ProjectGetter.promises.findUsersProjectsByName(
ctx.userId,
ctx.project1.name
)
const projectNames = projects.map(project => project.name)
expect(projectNames).to.have.members([
this.project1.name,
this.project3.name,
this.project4.name,
ctx.project1.name,
ctx.project3.name,
ctx.project4.name,
])
})
it('should search collaborations as well', async function () {
this.project1 = { _id: 1, name: 'find me!' }
this.project2 = { _id: 2, name: 'FIND ME!' }
this.project3 = { _id: 3, name: 'Find Me!' }
this.project4 = { _id: 4, name: 'find ME!' }
this.project5 = { _id: 5, name: 'FIND me!' }
this.Project.find
.withArgs({ owner_ref: this.userId })
.returns({ exec: sinon.stub().resolves([this.project1]) })
this.CollaboratorsGetter.promises.getProjectsUserIsMemberOf.resolves({
readAndWrite: [this.project2],
readOnly: [this.project3],
tokenReadAndWrite: [this.project4],
tokenReadOnly: [this.project5],
it('should search collaborations as well', async function (ctx) {
ctx.project1 = { _id: 1, name: 'find me!' }
ctx.project2 = { _id: 2, name: 'FIND ME!' }
ctx.project3 = { _id: 3, name: 'Find Me!' }
ctx.project4 = { _id: 4, name: 'find ME!' }
ctx.project5 = { _id: 5, name: 'FIND me!' }
ctx.Project.find
.withArgs({ owner_ref: ctx.userId })
.returns({ exec: sinon.stub().resolves([ctx.project1]) })
ctx.CollaboratorsGetter.promises.getProjectsUserIsMemberOf.resolves({
readAndWrite: [ctx.project2],
readOnly: [ctx.project3],
tokenReadAndWrite: [ctx.project4],
tokenReadOnly: [ctx.project5],
})
const projects =
await this.ProjectGetter.promises.findUsersProjectsByName(
this.userId,
this.project1.name
)
const projects = await ctx.ProjectGetter.promises.findUsersProjectsByName(
ctx.userId,
ctx.project1.name
)
expect(projects.map(project => project.name)).to.have.members([
this.project1.name,
this.project2.name,
ctx.project1.name,
ctx.project2.name,
])
})
})
describe('getUsersDeletedProjects', function () {
it('should look up the deleted projects by deletedProjectOwnerId', async function () {
await this.ProjectGetter.promises.getUsersDeletedProjects('giraffe')
sinon.assert.calledWith(this.DeletedProject.find, {
it('should look up the deleted projects by deletedProjectOwnerId', async function (ctx) {
await ctx.ProjectGetter.promises.getUsersDeletedProjects('giraffe')
sinon.assert.calledWith(ctx.DeletedProject.find, {
'deleterData.deletedProjectOwnerId': 'giraffe',
})
})
it('should pass the found projects to the callback', async function () {
it('should pass the found projects to the callback', async function (ctx) {
const docs =
await this.ProjectGetter.promises.getUsersDeletedProjects('giraffe')
expect(docs).to.deep.equal([this.deletedProject])
await ctx.ProjectGetter.promises.getUsersDeletedProjects('giraffe')
expect(docs).to.deep.equal([ctx.deletedProject])
})
})
})
@@ -46,11 +46,14 @@ describe('ProjectHistoryHandler', function () {
})
)
vi.doMock('../../../../app/src/Features/History/HistoryManager.mjs', () => ({
default: (ctx.HistoryManager = {
promises: {},
}),
}))
vi.doMock(
'../../../../app/src/Features/History/HistoryManager.mjs',
() => ({
default: (ctx.HistoryManager = {
promises: {},
}),
})
)
vi.doMock(
'../../../../app/src/Features/Project/ProjectEntityUpdateHandler',
@@ -5,10 +5,13 @@ import Errors from '../../../../app/src/Features/Errors/Errors.js'
const ObjectId = mongodb.ObjectId
const MODULE_PATH = new URL(
'../../../../app/src/Features/Project/ProjectListController',
import.meta.url
).pathname
const MODULE_PATH = `${import.meta.dirname}/../../../../app/src/Features/Project/ProjectListController`
// Mock AnalyticsManager as it isn't used in these tests but causes the User model to be imported
// TODO: remove this once all models are ESM and this kind of mocking is no longer necessary
vi.mock('../../../../app/src/Features/Analytics/AnalyticsManager.js', () => {
return {}
})
describe('ProjectListController', function () {
beforeEach(async function (ctx) {
@@ -1,8 +1,11 @@
const { expect } = require('chai')
import { vi, expect } from 'vitest'
import sinon from 'sinon'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
const modulePath = '../../../../app/src/Features/Project/ProjectLocator'
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const Errors = require('../../../../app/src/Features/Errors/Errors')
vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
vi.importActual('../../../../app/src/Features/Errors/Errors.js')
)
const project = { _id: '1234566', rootFolder: [] }
const rootDoc = { name: 'rootDoc', _id: 'das239djd' }
@@ -38,47 +41,50 @@ project.rootFolder[0] = rootFolder
project.rootDoc_id = rootDoc._id
describe('ProjectLocator', function () {
beforeEach(function () {
this.ProjectGetter = {
beforeEach(async function (ctx) {
ctx.ProjectGetter = {
getProject: sinon.stub().callsArgWith(2, null, project),
}
this.ProjectHelper = {
ctx.ProjectHelper = {
isArchived: sinon.stub(),
isTrashed: sinon.stub(),
isArchivedOrTrashed: sinon.stub(),
}
this.locator = SandboxedModule.require(modulePath, {
requires: {
'../../models/User': { User: this.User },
'./ProjectGetter': this.ProjectGetter,
'./ProjectHelper': this.ProjectHelper,
},
})
vi.doMock('../../../../app/src/models/User', () => ({
default: { User: ctx.User },
}))
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
default: ctx.ProjectGetter,
}))
vi.doMock('../../../../app/src/Features/Project/ProjectHelper', () => ({
default: ctx.ProjectHelper,
}))
ctx.locator = (await import(modulePath)).default
})
describe('finding a doc', function () {
it('finds one at the root level', async function () {
const { element, path, folder } = await this.locator.promises.findElement(
{
project_id: project._id,
element_id: doc2._id,
type: 'docs',
}
)
it('finds one at the root level', async function (ctx) {
const { element, path, folder } = await ctx.locator.promises.findElement({
project_id: project._id,
element_id: doc2._id,
type: 'docs',
})
element._id.should.equal(doc2._id)
path.fileSystem.should.equal(`/${doc2.name}`)
folder._id.should.equal(project.rootFolder[0]._id)
path.mongo.should.equal('rootFolder.0.docs.1')
})
it('when it is nested', async function () {
const { element, path, folder } = await this.locator.promises.findElement(
{
project_id: project._id,
element_id: subSubDoc._id,
type: 'doc',
}
)
it('when it is nested', async function (ctx) {
const { element, path, folder } = await ctx.locator.promises.findElement({
project_id: project._id,
element_id: subSubDoc._id,
type: 'doc',
})
expect(element._id).to.equal(subSubDoc._id)
path.fileSystem.should.equal(
`/${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
@@ -87,9 +93,9 @@ describe('ProjectLocator', function () {
path.mongo.should.equal('rootFolder.0.folders.1.folders.0.docs.0')
})
it('should give error if element could not be found', async function () {
it('should give error if element could not be found', async function (ctx) {
await expect(
this.locator.promises.findElement({
ctx.locator.promises.findElement({
project_id: project._id,
element_id: 'ddsd432nj42',
type: 'docs',
@@ -101,20 +107,18 @@ describe('ProjectLocator', function () {
})
describe('finding a folder', function () {
it('should return root folder when looking for root folder', async function () {
const { element: foundElement } = await this.locator.promises.findElement(
{
project_id: project._id,
element_id: rootFolder._id,
type: 'folder',
}
)
it('should return root folder when looking for root folder', async function (ctx) {
const { element: foundElement } = await ctx.locator.promises.findElement({
project_id: project._id,
element_id: rootFolder._id,
type: 'folder',
})
foundElement._id.should.equal(rootFolder._id)
})
it('should not return root folder when searching for docs', async function () {
it('should not return root folder when searching for docs', async function (ctx) {
await expect(
this.locator.promises.findElement({
ctx.locator.promises.findElement({
project_id: project._id,
element_id: rootFolder._id,
type: 'docs',
@@ -124,9 +128,9 @@ describe('ProjectLocator', function () {
.and.eventually.have.property('message', 'entity not found')
})
it('should not return root folder when searching for files', async function () {
it('should not return root folder when searching for files', async function (ctx) {
await expect(
this.locator.promises.findElement({
ctx.locator.promises.findElement({
project_id: project._id,
element_id: rootFolder._id,
type: 'files',
@@ -136,12 +140,12 @@ describe('ProjectLocator', function () {
.and.eventually.have.property('message', 'entity not found')
})
it('when at root', async function () {
it('when at root', async function (ctx) {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
} = await ctx.locator.promises.findElement({
project_id: project._id,
element_id: subFolder._id,
type: 'folder',
@@ -152,12 +156,12 @@ describe('ProjectLocator', function () {
path.mongo.should.equal('rootFolder.0.folders.1')
})
it('when deeply nested', async function () {
it('when deeply nested', async function (ctx) {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
} = await ctx.locator.promises.findElement({
project_id: project._id,
element_id: secondSubFolder._id,
type: 'folder',
@@ -171,12 +175,12 @@ describe('ProjectLocator', function () {
})
describe('finding a file', function () {
it('when at root', async function () {
it('when at root', async function (ctx) {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
} = await ctx.locator.promises.findElement({
project_id: project._id,
element_id: file1._id,
type: 'fileRefs',
@@ -187,12 +191,12 @@ describe('ProjectLocator', function () {
path.mongo.should.equal('rootFolder.0.fileRefs.0')
})
it('when deeply nested', async function () {
it('when deeply nested', async function (ctx) {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
} = await ctx.locator.promises.findElement({
project_id: project._id,
element_id: subSubFile._id,
type: 'fileRefs',
@@ -207,25 +211,21 @@ describe('ProjectLocator', function () {
})
describe('finding an element with wrong element type', function () {
it('should add an s onto the element type', async function () {
const { element: foundElement } = await this.locator.promises.findElement(
{
project_id: project._id,
element_id: subSubDoc._id,
type: 'doc',
}
)
it('should add an s onto the element type', async function (ctx) {
const { element: foundElement } = await ctx.locator.promises.findElement({
project_id: project._id,
element_id: subSubDoc._id,
type: 'doc',
})
foundElement._id.should.equal(subSubDoc._id)
})
it('should convert file to fileRefs', async function () {
const { element: foundElement } = await this.locator.promises.findElement(
{
project_id: project._id,
element_id: file1._id,
type: 'fileRefs',
}
)
it('should convert file to fileRefs', async function (ctx) {
const { element: foundElement } = await ctx.locator.promises.findElement({
project_id: project._id,
element_id: file1._id,
type: 'fileRefs',
})
foundElement._id.should.equal(file1._id)
})
})
@@ -243,12 +243,12 @@ describe('ProjectLocator', function () {
_id: '1234566',
rootFolder: [rootFolder2],
}
it('should find doc in project', async function () {
it('should find doc in project', async function (ctx) {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
} = await ctx.locator.promises.findElement({
project: project2,
element_id: doc3._id,
type: 'docs',
@@ -261,38 +261,38 @@ describe('ProjectLocator', function () {
})
describe('finding root doc', function () {
it('should return root doc when passed project', async function () {
const { element: doc } = await this.locator.promises.findRootDoc(project)
it('should return root doc when passed project', async function (ctx) {
const { element: doc } = await ctx.locator.promises.findRootDoc(project)
doc._id.should.equal(rootDoc._id)
})
it('should return root doc when passed project_id', async function () {
const { element: doc } = await this.locator.promises.findRootDoc(
it('should return root doc when passed project_id', async function (ctx) {
const { element: doc } = await ctx.locator.promises.findRootDoc(
project._id
)
doc._id.should.equal(rootDoc._id)
})
it('should return null when the project has no rootDoc', async function () {
it('should return null when the project has no rootDoc', async function (ctx) {
project.rootDoc_id = null
const { element: rootDoc } =
await this.locator.promises.findRootDoc(project)
await ctx.locator.promises.findRootDoc(project)
expect(rootDoc).to.equal(null)
})
it('should return null when the rootDoc_id no longer exists', async function () {
it('should return null when the rootDoc_id no longer exists', async function (ctx) {
project.rootDoc_id = 'doesntexist'
const { element: rootDoc } =
await this.locator.promises.findRootDoc(project)
await ctx.locator.promises.findRootDoc(project)
expect(rootDoc).to.equal(null)
})
})
describe('findElementByPath', function () {
it('should take a doc path and return the element for a root level document', async function () {
it('should take a doc path and return the element for a root level document', async function (ctx) {
const path = `${doc1.name}`
const { element, type, folder } =
await this.locator.promises.findElementByPath({
await ctx.locator.promises.findElementByPath({
project,
path,
})
@@ -301,10 +301,10 @@ describe('ProjectLocator', function () {
expect(folder).to.equal(rootFolder)
})
it('should take a doc path and return the element for a root level document with a starting slash', async function () {
it('should take a doc path and return the element for a root level document with a starting slash', async function (ctx) {
const path = `/${doc1.name}`
const { element, type, folder } =
await this.locator.promises.findElementByPath({
await ctx.locator.promises.findElementByPath({
project,
path,
})
@@ -313,10 +313,10 @@ describe('ProjectLocator', function () {
expect(folder).to.equal(rootFolder)
})
it('should take a doc path and return the element for a nested document', async function () {
it('should take a doc path and return the element for a nested document', async function (ctx) {
const path = `${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
const { element, type, folder } =
await this.locator.promises.findElementByPath({
await ctx.locator.promises.findElementByPath({
project,
path,
})
@@ -325,10 +325,10 @@ describe('ProjectLocator', function () {
expect(folder).to.equal(secondSubFolder)
})
it('should take a file path and return the element for a root level document', async function () {
it('should take a file path and return the element for a root level document', async function (ctx) {
const path = `${file1.name}`
const { element, type, folder } =
await this.locator.promises.findElementByPath({
await ctx.locator.promises.findElementByPath({
project,
path,
})
@@ -337,10 +337,10 @@ describe('ProjectLocator', function () {
expect(folder).to.equal(rootFolder)
})
it('should take a file path and return the element for a nested document', async function () {
it('should take a file path and return the element for a nested document', async function (ctx) {
const path = `${subFolder.name}/${secondSubFolder.name}/${subSubFile.name}`
const { element, type, folder } =
await this.locator.promises.findElementByPath({
await ctx.locator.promises.findElementByPath({
project,
path,
})
@@ -349,10 +349,10 @@ describe('ProjectLocator', function () {
expect(folder).to.equal(secondSubFolder)
})
it('should take a file path and return the element for a nested document case insenstive', async function () {
it('should take a file path and return the element for a nested document case insenstive', async function (ctx) {
const path = `${subFolder.name.toUpperCase()}/${secondSubFolder.name.toUpperCase()}/${subSubFile.name.toUpperCase()}`
const { element, type, folder } =
await this.locator.promises.findElementByPath({
await ctx.locator.promises.findElementByPath({
project,
path,
})
@@ -361,10 +361,10 @@ describe('ProjectLocator', function () {
expect(folder).to.equal(secondSubFolder)
})
it('should not return elements with a case-insensitive match when exactCaseMatch is true', async function () {
it('should not return elements with a case-insensitive match when exactCaseMatch is true', async function (ctx) {
const path = `${subFolder.name.toUpperCase()}/${secondSubFolder.name.toUpperCase()}/${subSubFile.name.toUpperCase()}`
await expect(
this.locator.promises.findElementByPath({
ctx.locator.promises.findElementByPath({
project,
path,
exactCaseMatch: true,
@@ -372,10 +372,10 @@ describe('ProjectLocator', function () {
).to.eventually.be.rejected
})
it('should take a file path and return the element for a nested folder', async function () {
it('should take a file path and return the element for a nested folder', async function (ctx) {
const path = `${subFolder.name}/${secondSubFolder.name}`
const { element, type, folder } =
await this.locator.promises.findElementByPath({
await ctx.locator.promises.findElementByPath({
project,
path,
})
@@ -384,10 +384,10 @@ describe('ProjectLocator', function () {
expect(folder).to.equal(subFolder)
})
it('should take a file path and return the root folder', async function () {
it('should take a file path and return the root folder', async function (ctx) {
const path = '/'
const { element, type, folder } =
await this.locator.promises.findElementByPath({
await ctx.locator.promises.findElementByPath({
project,
path,
})
@@ -396,16 +396,16 @@ describe('ProjectLocator', function () {
expect(folder).to.equal(null)
})
it('should return an error if the file can not be found inside know folder', async function () {
it('should return an error if the file can not be found inside know folder', async function (ctx) {
const path = `${subFolder.name}/${secondSubFolder.name}/exist.txt`
await expect(this.locator.promises.findElementByPath({ project, path }))
.to.eventually.be.rejected
await expect(ctx.locator.promises.findElementByPath({ project, path })).to
.eventually.be.rejected
})
it('should return an error if the file can not be found inside unknown folder', async function () {
it('should return an error if the file can not be found inside unknown folder', async function (ctx) {
const path = 'this/does/not/exist.txt'
await expect(
this.locator.promises.findElementByPath({
ctx.locator.promises.findElementByPath({
project,
path,
})
@@ -413,8 +413,8 @@ describe('ProjectLocator', function () {
})
describe('where duplicate folder exists', function () {
beforeEach(function () {
this.duplicateFolder = {
beforeEach(function (ctx) {
ctx.duplicateFolder = {
name: 'duplicate1',
_id: '1234',
folders: [
@@ -425,13 +425,13 @@ describe('ProjectLocator', function () {
fileRefs: [],
},
],
docs: [(this.doc = { name: 'main.tex', _id: '456' })],
docs: [(ctx.doc = { name: 'main.tex', _id: '456' })],
fileRefs: [],
}
this.project = {
ctx.project = {
rootFolder: [
{
folders: [this.duplicateFolder, this.duplicateFolder],
folders: [ctx.duplicateFolder, ctx.duplicateFolder],
fileRefs: [],
docs: [],
},
@@ -439,26 +439,26 @@ describe('ProjectLocator', function () {
}
})
it('should not call the callback more than once', async function () {
const path = `${this.duplicateFolder.name}/${this.doc.name}`
await this.locator.promises.findElementByPath({
project: this.project,
it('should not call the callback more than once', async function (ctx) {
const path = `${ctx.duplicateFolder.name}/${ctx.doc.name}`
await ctx.locator.promises.findElementByPath({
project: ctx.project,
path,
})
}) // mocha will throw exception if done called multiple times
it('should not call the callback more than once when the path is longer than 1 level below the duplicate level', async function () {
const path = `${this.duplicateFolder.name}/1/main.tex`
await this.locator.promises.findElementByPath({
project: this.project,
it('should not call the callback more than once when the path is longer than 1 level below the duplicate level', async function (ctx) {
const path = `${ctx.duplicateFolder.name}/1/main.tex`
await ctx.locator.promises.findElementByPath({
project: ctx.project,
path,
})
})
}) // mocha will throw exception if done called multiple times
describe('with a null doc', function () {
beforeEach(function () {
this.project = {
beforeEach(function (ctx) {
ctx.project = {
rootFolder: [
{
folders: [],
@@ -469,10 +469,10 @@ describe('ProjectLocator', function () {
}
})
it('should not crash with a null', async function () {
it('should not crash with a null', async function (ctx) {
const path = '/other.tex'
const { element } = await this.locator.promises.findElementByPath({
project: this.project,
const { element } = await ctx.locator.promises.findElementByPath({
project: ctx.project,
path,
})
element.name.should.equal('other.tex')
@@ -480,14 +480,14 @@ describe('ProjectLocator', function () {
})
describe('with a null project', function () {
beforeEach(function () {
this.ProjectGetter = { getProject: sinon.stub().callsArg(2) }
beforeEach(function (ctx) {
ctx.ProjectGetter = { getProject: sinon.stub().callsArg(2) }
})
it('should not crash with a null', async function () {
it('should not crash with a null', async function (ctx) {
const path = '/other.tex'
await expect(
this.locator.promises.findElementByPath({
ctx.locator.promises.findElementByPath({
project_id: project._id,
path,
})
@@ -496,15 +496,13 @@ describe('ProjectLocator', function () {
})
describe('with a project_id', function () {
it('should take a doc path and return the element for a root level document', async function () {
it('should take a doc path and return the element for a root level document', async function (ctx) {
const path = `${doc1.name}`
const { element, type } = await this.locator.promises.findElementByPath(
{
project_id: project._id,
path,
}
)
this.ProjectGetter.getProject
const { element, type } = await ctx.locator.promises.findElementByPath({
project_id: project._id,
path,
})
ctx.ProjectGetter.getProject
.calledWith(project._id, { rootFolder: true, rootDoc_id: true })
.should.equal(true)
element.should.deep.equal(doc1)
@@ -514,17 +512,17 @@ describe('ProjectLocator', function () {
})
describe('findElementByMongoPath', function () {
it('traverses the file tree like Mongo would do', function () {
const element = this.locator.findElementByMongoPath(
it('traverses the file tree like Mongo would do', function (ctx) {
const element = ctx.locator.findElementByMongoPath(
project,
'rootFolder.0.folders.1.folders.0.fileRefs.0'
)
expect(element).to.equal(subSubFile)
})
it('throws an error if no element is found', function () {
it('throws an error if no element is found', function (ctx) {
expect(() =>
this.locator.findElementByMongoPath(
ctx.locator.findElementByMongoPath(
project,
'rootolder.0.folders.0.folders.0.fileRefs.0'
)
@@ -1,60 +1,81 @@
const { expect } = require('chai')
const { ObjectId } = require('mongodb-legacy')
const sinon = require('sinon')
import { vi, expect } from 'vitest'
import mongodb from 'mongodb-legacy'
import sinon from 'sinon'
const modulePath =
'../../../../app/src/Features/Project/ProjectRootDocManager.js'
const SandboxedModule = require('sandboxed-module')
'../../../../app/src/Features/Project/ProjectRootDocManager.mjs'
const { ObjectId } = mongodb
describe('ProjectRootDocManager', function () {
beforeEach(function () {
this.project_id = 'project-123'
this.docPaths = {}
this.docId1 = new ObjectId()
this.docId2 = new ObjectId()
this.docId3 = new ObjectId()
this.docId4 = new ObjectId()
this.docPaths[this.docId1] = '/chapter1.tex'
this.docPaths[this.docId2] = '/main.tex'
this.docPaths[this.docId3] = '/nested/chapter1a.tex'
this.docPaths[this.docId4] = '/nested/chapter1b.tex'
this.sl_req_id = 'sl-req-id-123'
this.callback = sinon.stub()
this.globbyFiles = ['a.tex', 'b.tex', 'main.tex']
this.globby = sinon.stub().resolves(this.globbyFiles)
beforeEach(async function (ctx) {
ctx.project_id = 'project-123'
ctx.docPaths = {}
ctx.docId1 = new ObjectId()
ctx.docId2 = new ObjectId()
ctx.docId3 = new ObjectId()
ctx.docId4 = new ObjectId()
ctx.docPaths[ctx.docId1] = '/chapter1.tex'
ctx.docPaths[ctx.docId2] = '/main.tex'
ctx.docPaths[ctx.docId3] = '/nested/chapter1a.tex'
ctx.docPaths[ctx.docId4] = '/nested/chapter1b.tex'
ctx.sl_req_id = 'sl-req-id-123'
ctx.callback = sinon.stub()
ctx.globbyFiles = ['a.tex', 'b.tex', 'main.tex']
ctx.globby = sinon.stub().resolves(ctx.globbyFiles)
this.fs = {
ctx.fs = {
readFile: sinon.stub().callsArgWith(2, new Error('file not found')),
stat: sinon.stub().callsArgWith(1, null, { size: 100 }),
}
this.ProjectRootDocManager = SandboxedModule.require(modulePath, {
requires: {
'./ProjectEntityHandler': (this.ProjectEntityHandler = {}),
'./ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {}),
'./ProjectGetter': (this.ProjectGetter = {}),
'../../infrastructure/GracefulShutdown': {
BackgroundTaskTracker: class {
add() {}
done() {}
},
},
globby: this.globby,
fs: this.fs,
vi.doMock(
'../../../../app/src/Features/Project/ProjectEntityHandler',
() => ({
default: (ctx.ProjectEntityHandler = {}),
})
)
vi.doMock(
'../../../../app/src/Features/Project/ProjectEntityUpdateHandler',
() => ({
default: (ctx.ProjectEntityUpdateHandler = {}),
})
)
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
default: (ctx.ProjectGetter = {}),
}))
vi.doMock('../../../../app/src/infrastructure/GracefulShutdown', () => ({
BackgroundTaskTracker: class {
add() {}
done() {}
},
})
}))
vi.doMock('globby', () => ({
default: ctx.globby,
}))
vi.doMock('fs', () => ({
default: ctx.fs,
}))
ctx.ProjectRootDocManager = (await import(modulePath)).default
})
describe('setRootDocAutomatically', function () {
beforeEach(function () {
this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
this.ProjectEntityUpdateHandler.isPathValidForRootDoc = sinon
beforeEach(function (ctx) {
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
ctx.ProjectEntityUpdateHandler.isPathValidForRootDoc = sinon
.stub()
.returns(true)
})
describe('when there is a suitable root doc', function () {
beforeEach(async function () {
this.docs = {
beforeEach(async function (ctx) {
ctx.docs = {
'/chapter1.tex': {
_id: this.docId1,
_id: ctx.docId1,
lines: [
'something else',
'\\begin{document}',
@@ -63,7 +84,7 @@ describe('ProjectRootDocManager', function () {
],
},
'/main.tex': {
_id: this.docId2,
_id: ctx.docId2,
lines: [
'different line',
'\\documentclass{article}',
@@ -71,114 +92,114 @@ describe('ProjectRootDocManager', function () {
],
},
'/nested/chapter1a.tex': {
_id: this.docId3,
_id: ctx.docId3,
lines: ['Hello world'],
},
'/nested/chapter1b.tex': {
_id: this.docId4,
_id: ctx.docId4,
lines: ['Hello world'],
},
}
this.ProjectEntityHandler.getAllDocs = sinon
ctx.ProjectEntityHandler.getAllDocs = sinon
.stub()
.callsArgWith(1, null, this.docs)
await this.ProjectRootDocManager.promises.setRootDocAutomatically(
this.project_id
.callsArgWith(1, null, ctx.docs)
await ctx.ProjectRootDocManager.promises.setRootDocAutomatically(
ctx.project_id
)
})
it('should check the docs of the project', function () {
this.ProjectEntityHandler.getAllDocs
.calledWith(this.project_id)
it('should check the docs of the project', function (ctx) {
ctx.ProjectEntityHandler.getAllDocs
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should set the root doc to the doc containing a documentclass', function () {
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2)
it('should set the root doc to the doc containing a documentclass', function (ctx) {
ctx.ProjectEntityUpdateHandler.setRootDoc
.calledWith(ctx.project_id, ctx.docId2)
.should.equal(true)
})
})
describe('when the root doc is an Rtex file', function () {
beforeEach(async function () {
this.docs = {
beforeEach(async function (ctx) {
ctx.docs = {
'/chapter1.tex': {
_id: this.docId1,
_id: ctx.docId1,
lines: ['\\begin{document}', 'Hello world', '\\end{document}'],
},
'/main.Rtex': {
_id: this.docId2,
_id: ctx.docId2,
lines: ['\\documentclass{article}', '\\input{chapter1}'],
},
}
this.ProjectEntityHandler.getAllDocs = sinon
ctx.ProjectEntityHandler.getAllDocs = sinon
.stub()
.callsArgWith(1, null, this.docs)
await this.ProjectRootDocManager.promises.setRootDocAutomatically(
this.project_id
.callsArgWith(1, null, ctx.docs)
await ctx.ProjectRootDocManager.promises.setRootDocAutomatically(
ctx.project_id
)
})
it('should set the root doc to the doc containing a documentclass', function () {
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2)
it('should set the root doc to the doc containing a documentclass', function (ctx) {
ctx.ProjectEntityUpdateHandler.setRootDoc
.calledWith(ctx.project_id, ctx.docId2)
.should.equal(true)
})
})
describe('when there is no suitable root doc', function () {
beforeEach(async function () {
this.docs = {
beforeEach(async function (ctx) {
ctx.docs = {
'/chapter1.tex': {
_id: this.docId1,
_id: ctx.docId1,
lines: ['\\begin{document}', 'Hello world', '\\end{document}'],
},
'/style.bst': {
_id: this.docId2,
_id: ctx.docId2,
lines: ['%Example: \\documentclass{article}'],
},
}
this.ProjectEntityHandler.getAllDocs = sinon
ctx.ProjectEntityHandler.getAllDocs = sinon
.stub()
.callsArgWith(1, null, this.docs)
await this.ProjectRootDocManager.promises.setRootDocAutomatically(
this.project_id
.callsArgWith(1, null, ctx.docs)
await ctx.ProjectRootDocManager.promises.setRootDocAutomatically(
ctx.project_id
)
})
it('should not set the root doc to the doc containing a documentclass', function () {
this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
it('should not set the root doc to the doc containing a documentclass', function (ctx) {
ctx.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
})
})
})
describe('findRootDocFileFromDirectory', function () {
beforeEach(function () {
this.fs.readFile
beforeEach(function (ctx) {
ctx.fs.readFile
.withArgs('/foo/a.tex')
.callsArgWith(2, null, 'Hello World!')
this.fs.readFile
ctx.fs.readFile
.withArgs('/foo/b.tex')
.callsArgWith(2, null, "I'm a little teapot, get me out of here.")
this.fs.readFile
ctx.fs.readFile
.withArgs('/foo/main.tex')
.callsArgWith(2, null, "Help, I'm trapped in a unit testing factory")
this.fs.readFile
ctx.fs.readFile
.withArgs('/foo/c.tex')
.callsArgWith(2, null, 'Tomato, tomahto.')
this.fs.readFile
ctx.fs.readFile
.withArgs('/foo/a/a.tex')
.callsArgWith(2, null, 'Potato? Potahto. Potootee!')
this.documentclassContent = '% test\n\\documentclass\n% test'
ctx.documentclassContent = '% test\n\\documentclass\n% test'
})
describe('when there is a file in a subfolder', function () {
beforeEach(function () {
beforeEach(function (ctx) {
// have to splice globbyFiles weirdly because of the way the stubbed globby method handles references
this.globbyFiles.splice(
ctx.globbyFiles.splice(
0,
this.globbyFiles.length,
ctx.globbyFiles.length,
'c.tex',
'a.tex',
'a/a.tex',
@@ -186,90 +207,90 @@ describe('ProjectRootDocManager', function () {
)
})
it('processes the root folder files first, and then the subfolder, in alphabetical order', async function () {
it('processes the root folder files first, and then the subfolder, in alphabetical order', async function (ctx) {
const { path } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('a.tex')
sinon.assert.callOrder(
this.fs.readFile.withArgs('/foo/a.tex'),
this.fs.readFile.withArgs('/foo/b.tex'),
this.fs.readFile.withArgs('/foo/c.tex'),
this.fs.readFile.withArgs('/foo/a/a.tex')
ctx.fs.readFile.withArgs('/foo/a.tex'),
ctx.fs.readFile.withArgs('/foo/b.tex'),
ctx.fs.readFile.withArgs('/foo/c.tex'),
ctx.fs.readFile.withArgs('/foo/a/a.tex')
)
})
it('processes smaller files first', async function () {
this.fs.stat.withArgs('/foo/c.tex').callsArgWith(1, null, { size: 1 })
it('processes smaller files first', async function (ctx) {
ctx.fs.stat.withArgs('/foo/c.tex').callsArgWith(1, null, { size: 1 })
const { path } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('c.tex')
sinon.assert.callOrder(
this.fs.readFile.withArgs('/foo/c.tex'),
this.fs.readFile.withArgs('/foo/a.tex'),
this.fs.readFile.withArgs('/foo/b.tex'),
this.fs.readFile.withArgs('/foo/a/a.tex')
ctx.fs.readFile.withArgs('/foo/c.tex'),
ctx.fs.readFile.withArgs('/foo/a.tex'),
ctx.fs.readFile.withArgs('/foo/b.tex'),
ctx.fs.readFile.withArgs('/foo/a/a.tex')
)
})
})
describe('when main.tex contains a documentclass', function () {
beforeEach(function () {
this.fs.readFile
beforeEach(function (ctx) {
ctx.fs.readFile
.withArgs('/foo/main.tex')
.callsArgWith(2, null, this.documentclassContent)
.callsArgWith(2, null, ctx.documentclassContent)
})
it('returns main.tex', async function () {
it('returns main.tex', async function (ctx) {
const { path, content } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('main.tex')
expect(content).to.equal(this.documentclassContent)
expect(content).to.equal(ctx.documentclassContent)
})
it('processes main.text first and stops processing when it finds the content', async function () {
await this.ProjectRootDocManager.findRootDocFileFromDirectory('/foo')
expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
expect(this.fs.readFile).not.to.be.calledWith('/foo/a.tex')
it('processes main.text first and stops processing when it finds the content', async function (ctx) {
await ctx.ProjectRootDocManager.findRootDocFileFromDirectory('/foo')
expect(ctx.fs.readFile).to.be.calledWith('/foo/main.tex')
expect(ctx.fs.readFile).not.to.be.calledWith('/foo/a.tex')
})
})
describe('when main.tex does not contain a line starting with \\documentclass', function () {
beforeEach(function () {
this.fs.readFile.withArgs('/foo/a.tex').callsArgWith(2, null, 'foo')
this.fs.readFile.withArgs('/foo/main.tex').callsArgWith(2, null, 'foo')
this.fs.readFile.withArgs('/foo/z.tex').callsArgWith(2, null, 'foo')
this.fs.readFile
beforeEach(function (ctx) {
ctx.fs.readFile.withArgs('/foo/a.tex').callsArgWith(2, null, 'foo')
ctx.fs.readFile.withArgs('/foo/main.tex').callsArgWith(2, null, 'foo')
ctx.fs.readFile.withArgs('/foo/z.tex').callsArgWith(2, null, 'foo')
ctx.fs.readFile
.withArgs('/foo/nested/chapter1a.tex')
.callsArgWith(2, null, 'foo')
})
it('returns the first .tex file from the root folder', async function () {
this.globbyFiles.splice(
it('returns the first .tex file from the root folder', async function (ctx) {
ctx.globbyFiles.splice(
0,
this.globbyFiles.length,
ctx.globbyFiles.length,
'a.tex',
'z.tex',
'nested/chapter1a.tex'
)
const { path, content } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('a.tex')
expect(content).to.equal('foo')
})
it('returns main.tex file from the root folder', async function () {
this.globbyFiles.splice(
it('returns main.tex file from the root folder', async function (ctx) {
ctx.globbyFiles.splice(
0,
this.globbyFiles.length,
ctx.globbyFiles.length,
'a.tex',
'z.tex',
'main.tex',
@@ -277,7 +298,7 @@ describe('ProjectRootDocManager', function () {
)
const { path, content } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('main.tex')
@@ -286,60 +307,60 @@ describe('ProjectRootDocManager', function () {
})
describe('when a.tex contains a documentclass', function () {
beforeEach(function () {
this.fs.readFile
beforeEach(function (ctx) {
ctx.fs.readFile
.withArgs('/foo/a.tex')
.callsArgWith(2, null, this.documentclassContent)
.callsArgWith(2, null, ctx.documentclassContent)
})
it('returns a.tex', async function () {
it('returns a.tex', async function (ctx) {
const { path, content } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('a.tex')
expect(content).to.equal(this.documentclassContent)
expect(content).to.equal(ctx.documentclassContent)
})
it('processes main.text first and stops processing when it finds the content', async function () {
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
it('processes main.text first and stops processing when it finds the content', async function (ctx) {
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
expect(this.fs.readFile).not.to.be.calledWith('/foo/b.tex')
expect(ctx.fs.readFile).to.be.calledWith('/foo/main.tex')
expect(ctx.fs.readFile).to.be.calledWith('/foo/a.tex')
expect(ctx.fs.readFile).not.to.be.calledWith('/foo/b.tex')
})
})
describe('when there is no documentclass', function () {
it('returns with no error', async function () {
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
it('returns with no error', async function (ctx) {
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
})
it('processes all the files', async function () {
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
it('processes all the files', async function (ctx) {
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
expect(this.fs.readFile).to.be.calledWith('/foo/b.tex')
expect(ctx.fs.readFile).to.be.calledWith('/foo/main.tex')
expect(ctx.fs.readFile).to.be.calledWith('/foo/a.tex')
expect(ctx.fs.readFile).to.be.calledWith('/foo/b.tex')
})
})
describe('when there is an error reading a file', function () {
beforeEach(function () {
this.fs.readFile
beforeEach(function (ctx) {
ctx.fs.readFile
.withArgs('/foo/a.tex')
.callsArgWith(2, new Error('something went wrong'))
})
it('returns an error', async function () {
it('returns an error', async function (ctx) {
let error
try {
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
} catch (err) {
@@ -353,206 +374,196 @@ describe('ProjectRootDocManager', function () {
describe('setRootDocFromName', function () {
describe('when there is a suitable root doc', function () {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
beforeEach(async function (ctx) {
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
.callsArgWith(1, null, ctx.docPaths)
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
ctx.project_id,
'/main.tex'
)
})
it('should check the docs of the project', function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
it('should check the docs of the project', function (ctx) {
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should set the root doc to main.tex', function () {
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2.toString())
it('should set the root doc to main.tex', function (ctx) {
ctx.ProjectEntityUpdateHandler.setRootDoc
.calledWith(ctx.project_id, ctx.docId2.toString())
.should.equal(true)
})
})
describe('when there is a suitable root doc but the leading slash is missing', function () {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
beforeEach(async function (ctx) {
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
.callsArgWith(1, null, ctx.docPaths)
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
ctx.project_id,
'main.tex'
)
})
it('should check the docs of the project', function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
it('should check the docs of the project', function (ctx) {
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should set the root doc to main.tex', function () {
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2.toString())
it('should set the root doc to main.tex', function (ctx) {
ctx.ProjectEntityUpdateHandler.setRootDoc
.calledWith(ctx.project_id, ctx.docId2.toString())
.should.equal(true)
})
})
describe('when there is a suitable root doc with a basename match', function () {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
beforeEach(async function (ctx) {
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
.callsArgWith(1, null, ctx.docPaths)
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
ctx.project_id,
'chapter1a.tex'
)
})
it('should check the docs of the project', function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
it('should check the docs of the project', function (ctx) {
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should set the root doc using the basename', function () {
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId3.toString())
it('should set the root doc using the basename', function (ctx) {
ctx.ProjectEntityUpdateHandler.setRootDoc
.calledWith(ctx.project_id, ctx.docId3.toString())
.should.equal(true)
})
})
describe('when there is a suitable root doc but the filename is in quotes', function () {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
beforeEach(async function (ctx) {
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
.callsArgWith(1, null, ctx.docPaths)
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
ctx.project_id,
"'main.tex'"
)
})
it('should check the docs of the project', function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
it('should check the docs of the project', function (ctx) {
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should set the root doc to main.tex', function () {
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2.toString())
it('should set the root doc to main.tex', function (ctx) {
ctx.ProjectEntityUpdateHandler.setRootDoc
.calledWith(ctx.project_id, ctx.docId2.toString())
.should.equal(true)
})
})
describe('when there is no suitable root doc', function () {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
beforeEach(async function (ctx) {
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
.callsArgWith(1, null, ctx.docPaths)
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
ctx.project_id,
'other.tex'
)
})
it('should not set the root doc', function () {
this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
it('should not set the root doc', function (ctx) {
ctx.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
})
})
})
describe('ensureRootDocumentIsSet', function () {
beforeEach(function () {
this.project = {}
this.ProjectGetter.getProject = sinon
beforeEach(function (ctx) {
ctx.project = {}
ctx.ProjectGetter.getProject = sinon
.stub()
.callsArgWith(2, null, this.project)
this.ProjectRootDocManager.setRootDocAutomatically = sinon
.callsArgWith(2, null, ctx.project)
ctx.ProjectRootDocManager.setRootDocAutomatically = sinon
.stub()
.callsArgWith(1, null)
})
describe('when the root doc is set', function () {
beforeEach(function () {
this.project.rootDoc_id = this.docId2
this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
beforeEach(function (ctx) {
ctx.project.rootDoc_id = ctx.docId2
ctx.ProjectRootDocManager.ensureRootDocumentIsSet(
ctx.project_id,
ctx.callback
)
})
it('should find the project fetching only the rootDoc_id field', function () {
this.ProjectGetter.getProject
.calledWith(this.project_id, { rootDoc_id: 1 })
it('should find the project fetching only the rootDoc_id field', function (ctx) {
ctx.ProjectGetter.getProject
.calledWith(ctx.project_id, { rootDoc_id: 1 })
.should.equal(true)
})
it('should not try to update the project rootDoc_id', function () {
this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
it('should not try to update the project rootDoc_id', function (ctx) {
ctx.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
false
)
})
it('should call the callback', function () {
this.callback.called.should.equal(true)
it('should call the callback', function (ctx) {
ctx.callback.called.should.equal(true)
})
})
describe('when the root doc is not set', function () {
beforeEach(function () {
this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
beforeEach(function (ctx) {
ctx.ProjectRootDocManager.ensureRootDocumentIsSet(
ctx.project_id,
ctx.callback
)
})
it('should find the project with only the rootDoc_id field', function () {
this.ProjectGetter.getProject
.calledWith(this.project_id, { rootDoc_id: 1 })
it('should find the project with only the rootDoc_id field', function (ctx) {
ctx.ProjectGetter.getProject
.calledWith(ctx.project_id, { rootDoc_id: 1 })
.should.equal(true)
})
it('should update the project rootDoc_id', function () {
this.ProjectRootDocManager.setRootDocAutomatically
.calledWith(this.project_id)
it('should update the project rootDoc_id', function (ctx) {
ctx.ProjectRootDocManager.setRootDocAutomatically
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should call the callback', function () {
this.callback.called.should.equal(true)
it('should call the callback', function (ctx) {
ctx.callback.called.should.equal(true)
})
})
describe('when the project does not exist', function () {
beforeEach(function () {
this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
beforeEach(function (ctx) {
ctx.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
ctx.ProjectRootDocManager.ensureRootDocumentIsSet(
ctx.project_id,
ctx.callback
)
})
it('should call the callback with an error', function () {
this.callback
it('should call the callback with an error', function (ctx) {
ctx.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -564,125 +575,125 @@ describe('ProjectRootDocManager', function () {
})
describe('ensureRootDocumentIsValid', function () {
beforeEach(function () {
this.project = {}
this.ProjectGetter.getProject = sinon
beforeEach(function (ctx) {
ctx.project = {}
ctx.ProjectGetter.getProject = sinon
.stub()
.callsArgWith(2, null, this.project)
this.ProjectGetter.getProjectWithoutDocLines = sinon
.callsArgWith(2, null, ctx.project)
ctx.ProjectGetter.getProjectWithoutDocLines = sinon
.stub()
.callsArgWith(1, null, this.project)
this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().yields()
this.ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
this.ProjectRootDocManager.setRootDocAutomatically = sinon
.callsArgWith(1, null, ctx.project)
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().yields()
ctx.ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
ctx.ProjectRootDocManager.setRootDocAutomatically = sinon
.stub()
.callsArgWith(1, null)
})
describe('when the root doc is set', function () {
describe('when the root doc is valid', function () {
beforeEach(function () {
this.project.rootDoc_id = this.docId2
this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
beforeEach(function (ctx) {
ctx.project.rootDoc_id = ctx.docId2
ctx.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
.stub()
.callsArgWith(2, null, this.docPaths[this.docId2])
this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
.callsArgWith(2, null, ctx.docPaths[ctx.docId2])
ctx.ProjectRootDocManager.ensureRootDocumentIsValid(
ctx.project_id,
ctx.callback
)
})
it('should find the project without doc lines', function () {
this.ProjectGetter.getProjectWithoutDocLines
.calledWith(this.project_id)
it('should find the project without doc lines', function (ctx) {
ctx.ProjectGetter.getProjectWithoutDocLines
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should not try to update the project rootDoc_id', function () {
this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
it('should not try to update the project rootDoc_id', function (ctx) {
ctx.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
false
)
})
it('should call the callback', function () {
this.callback.called.should.equal(true)
it('should call the callback', function (ctx) {
ctx.callback.called.should.equal(true)
})
})
describe('when the root doc is not valid', function () {
beforeEach(function () {
this.project.rootDoc_id = new ObjectId()
this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
beforeEach(function (ctx) {
ctx.project.rootDoc_id = new ObjectId()
ctx.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
.stub()
.callsArgWith(2, null, null)
this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
ctx.ProjectRootDocManager.ensureRootDocumentIsValid(
ctx.project_id,
ctx.callback
)
})
it('should find the project without doc lines', function () {
this.ProjectGetter.getProjectWithoutDocLines
.calledWith(this.project_id)
it('should find the project without doc lines', function (ctx) {
ctx.ProjectGetter.getProjectWithoutDocLines
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should unset the root doc', function () {
this.ProjectEntityUpdateHandler.unsetRootDoc
.calledWith(this.project_id)
it('should unset the root doc', function (ctx) {
ctx.ProjectEntityUpdateHandler.unsetRootDoc
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should try to find a new rootDoc', function () {
this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
it('should try to find a new rootDoc', function (ctx) {
ctx.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
true
)
})
it('should call the callback', function () {
this.callback.called.should.equal(true)
it('should call the callback', function (ctx) {
ctx.callback.called.should.equal(true)
})
})
})
describe('when the root doc is not set', function () {
beforeEach(function () {
this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
beforeEach(function (ctx) {
ctx.ProjectRootDocManager.ensureRootDocumentIsValid(
ctx.project_id,
ctx.callback
)
})
it('should find the project without doc lines', function () {
this.ProjectGetter.getProjectWithoutDocLines
.calledWith(this.project_id)
it('should find the project without doc lines', function (ctx) {
ctx.ProjectGetter.getProjectWithoutDocLines
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should update the project rootDoc_id', function () {
this.ProjectRootDocManager.setRootDocAutomatically
.calledWith(this.project_id)
it('should update the project rootDoc_id', function (ctx) {
ctx.ProjectRootDocManager.setRootDocAutomatically
.calledWith(ctx.project_id)
.should.equal(true)
})
it('should call the callback', function () {
this.callback.called.should.equal(true)
it('should call the callback', function (ctx) {
ctx.callback.called.should.equal(true)
})
})
describe('when the project does not exist', function () {
beforeEach(function () {
this.ProjectGetter.getProjectWithoutDocLines = sinon
beforeEach(function (ctx) {
ctx.ProjectGetter.getProjectWithoutDocLines = sinon
.stub()
.callsArgWith(1, null, null)
this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
ctx.ProjectRootDocManager.ensureRootDocumentIsValid(
ctx.project_id,
ctx.callback
)
})
it('should call the callback with an error', function () {
this.callback
it('should call the callback with an error', function (ctx) {
ctx.callback
.calledWith(
sinon.match
.instanceOf(Error)