mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-28 19:41:33 +02:00
Convert tests to ESM
GitOrigin-RevId: 20585e01dee90e691476a0d47fd5c63b0412e4a6
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { expect, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import * as path from 'node:path'
|
||||
import sinon from 'sinon'
|
||||
import MockResponse from '../../../../../test/unit/src/helpers/MockResponse.js'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -476,13 +476,10 @@ describe('CollaboratorsController', function () {
|
||||
})
|
||||
|
||||
it('returns 204 on success', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.res.sendStatus = status => {
|
||||
expect(status).to.equal(204)
|
||||
resolve()
|
||||
}
|
||||
ctx.CollaboratorsController.transferOwnership(ctx.req, ctx.res)
|
||||
})
|
||||
ctx.res.sendStatus = vi.fn()
|
||||
|
||||
await ctx.CollaboratorsController.transferOwnership(ctx.req, ctx.res)
|
||||
expect(ctx.res.sendStatus).toHaveBeenCalledWith(204)
|
||||
})
|
||||
|
||||
it('returns 404 if the project does not exist', async function (ctx) {
|
||||
|
||||
@@ -1,46 +1,53 @@
|
||||
const Path = require('path')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
const { Project } = require('../helpers/models/Project')
|
||||
const Errors = require('../../../../app/src/Features/Errors/Errors')
|
||||
import { vi, expect } from 'vitest'
|
||||
import Path from 'path'
|
||||
import sinon from 'sinon'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import indirectlyImportModels from '../helpers/indirectlyImportModels.js'
|
||||
import Errors from '../../../../app/src/Features/Errors/Errors.js'
|
||||
|
||||
const { Project } = indirectlyImportModels(['Project'])
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
|
||||
vi.importActual('../../../../app/src/Features/Errors/Errors.js')
|
||||
)
|
||||
|
||||
const MODULE_PATH = Path.join(
|
||||
__dirname,
|
||||
import.meta.dirname,
|
||||
'../../../../app/src/Features/Collaborators/CollaboratorsGetter'
|
||||
)
|
||||
|
||||
describe('CollaboratorsGetter', function () {
|
||||
beforeEach(function () {
|
||||
this.userId = 'efb93a186e9a06f15fea5abd'
|
||||
this.ownerRef = new ObjectId()
|
||||
this.readOnlyRef1 = new ObjectId()
|
||||
this.readOnlyRef2 = new ObjectId()
|
||||
this.pendingEditorRef = new ObjectId()
|
||||
this.pendingReviewerRef = new ObjectId()
|
||||
this.readWriteRef1 = new ObjectId()
|
||||
this.readWriteRef2 = new ObjectId()
|
||||
this.reviewer1Ref = new ObjectId()
|
||||
this.reviewer2Ref = new ObjectId()
|
||||
this.readOnlyTokenRef = new ObjectId()
|
||||
this.readWriteTokenRef = new ObjectId()
|
||||
this.nonMemberRef = new ObjectId()
|
||||
this.project = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.userId = 'efb93a186e9a06f15fea5abd'
|
||||
ctx.ownerRef = new ObjectId()
|
||||
ctx.readOnlyRef1 = new ObjectId()
|
||||
ctx.readOnlyRef2 = new ObjectId()
|
||||
ctx.pendingEditorRef = new ObjectId()
|
||||
ctx.pendingReviewerRef = new ObjectId()
|
||||
ctx.readWriteRef1 = new ObjectId()
|
||||
ctx.readWriteRef2 = new ObjectId()
|
||||
ctx.reviewer1Ref = new ObjectId()
|
||||
ctx.reviewer2Ref = new ObjectId()
|
||||
ctx.readOnlyTokenRef = new ObjectId()
|
||||
ctx.readWriteTokenRef = new ObjectId()
|
||||
ctx.nonMemberRef = new ObjectId()
|
||||
ctx.project = {
|
||||
_id: new ObjectId(),
|
||||
owner_ref: [this.ownerRef],
|
||||
owner_ref: [ctx.ownerRef],
|
||||
readOnly_refs: [
|
||||
this.readOnlyRef1,
|
||||
this.readOnlyRef2,
|
||||
this.pendingEditorRef,
|
||||
this.pendingReviewerRef,
|
||||
ctx.readOnlyRef1,
|
||||
ctx.readOnlyRef2,
|
||||
ctx.pendingEditorRef,
|
||||
ctx.pendingReviewerRef,
|
||||
],
|
||||
pendingEditor_refs: [this.pendingEditorRef],
|
||||
pendingReviewer_refs: [this.pendingReviewerRef],
|
||||
collaberator_refs: [this.readWriteRef1, this.readWriteRef2],
|
||||
reviewer_refs: [this.reviewer1Ref, this.reviewer2Ref],
|
||||
tokenAccessReadAndWrite_refs: [this.readWriteTokenRef],
|
||||
tokenAccessReadOnly_refs: [this.readOnlyTokenRef],
|
||||
pendingEditor_refs: [ctx.pendingEditorRef],
|
||||
pendingReviewer_refs: [ctx.pendingReviewerRef],
|
||||
collaberator_refs: [ctx.readWriteRef1, ctx.readWriteRef2],
|
||||
reviewer_refs: [ctx.reviewer1Ref, ctx.reviewer2Ref],
|
||||
tokenAccessReadAndWrite_refs: [ctx.readWriteTokenRef],
|
||||
tokenAccessReadOnly_refs: [ctx.readOnlyTokenRef],
|
||||
publicAccesLevel: 'tokenBased',
|
||||
tokens: {
|
||||
readOnly: 'ro',
|
||||
@@ -49,98 +56,114 @@ describe('CollaboratorsGetter', function () {
|
||||
},
|
||||
}
|
||||
|
||||
this.UserGetter = {
|
||||
ctx.UserGetter = {
|
||||
promises: {
|
||||
getUser: sinon.stub().resolves(null),
|
||||
getUsers: sinon.stub().resolves([]),
|
||||
},
|
||||
}
|
||||
this.ProjectMock = sinon.mock(Project)
|
||||
this.ProjectGetter = {
|
||||
ctx.ProjectMock = sinon.mock(Project)
|
||||
ctx.ProjectGetter = {
|
||||
promises: {
|
||||
getProject: sinon.stub().resolves(this.project),
|
||||
getProject: sinon.stub().resolves(ctx.project),
|
||||
},
|
||||
}
|
||||
this.ProjectEditorHandler = {
|
||||
ctx.ProjectEditorHandler = {
|
||||
buildUserModelView: sinon.stub(),
|
||||
}
|
||||
this.CollaboratorsGetter = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'mongodb-legacy': { ObjectId },
|
||||
'../User/UserGetter': this.UserGetter,
|
||||
'../../models/Project': { Project },
|
||||
'../Project/ProjectGetter': this.ProjectGetter,
|
||||
'../Project/ProjectEditorHandler': this.ProjectEditorHandler,
|
||||
},
|
||||
})
|
||||
|
||||
vi.doMock('mongodb-legacy', () => ({
|
||||
default: { ObjectId },
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: ctx.UserGetter,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/models/Project', () => ({
|
||||
Project,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
|
||||
default: ctx.ProjectGetter,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectEditorHandler',
|
||||
() => ({
|
||||
default: ctx.ProjectEditorHandler,
|
||||
})
|
||||
)
|
||||
|
||||
ctx.CollaboratorsGetter = (await import(MODULE_PATH)).default
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.ProjectMock.verify()
|
||||
afterEach(function (ctx) {
|
||||
ctx.ProjectMock.verify()
|
||||
})
|
||||
|
||||
describe('getMemberIdsWithPrivilegeLevels', function () {
|
||||
describe('with project', function () {
|
||||
it('should return an array of member ids with their privilege levels', async function () {
|
||||
it('should return an array of member ids with their privilege levels', async function (ctx) {
|
||||
const result =
|
||||
await this.CollaboratorsGetter.promises.getMemberIdsWithPrivilegeLevels(
|
||||
this.project._id
|
||||
await ctx.CollaboratorsGetter.promises.getMemberIdsWithPrivilegeLevels(
|
||||
ctx.project._id
|
||||
)
|
||||
expect(result).to.have.deep.members([
|
||||
{
|
||||
id: this.ownerRef.toString(),
|
||||
id: ctx.ownerRef.toString(),
|
||||
privilegeLevel: 'owner',
|
||||
source: 'owner',
|
||||
},
|
||||
{
|
||||
id: this.readWriteRef1.toString(),
|
||||
id: ctx.readWriteRef1.toString(),
|
||||
privilegeLevel: 'readAndWrite',
|
||||
source: 'invite',
|
||||
},
|
||||
{
|
||||
id: this.readWriteRef2.toString(),
|
||||
id: ctx.readWriteRef2.toString(),
|
||||
privilegeLevel: 'readAndWrite',
|
||||
source: 'invite',
|
||||
},
|
||||
{
|
||||
id: this.readOnlyRef1.toString(),
|
||||
id: ctx.readOnlyRef1.toString(),
|
||||
privilegeLevel: 'readOnly',
|
||||
source: 'invite',
|
||||
},
|
||||
{
|
||||
id: this.readOnlyRef2.toString(),
|
||||
id: ctx.readOnlyRef2.toString(),
|
||||
privilegeLevel: 'readOnly',
|
||||
source: 'invite',
|
||||
},
|
||||
{
|
||||
id: this.pendingEditorRef.toString(),
|
||||
id: ctx.pendingEditorRef.toString(),
|
||||
privilegeLevel: 'readOnly',
|
||||
source: 'invite',
|
||||
pendingEditor: true,
|
||||
},
|
||||
{
|
||||
id: this.pendingReviewerRef.toString(),
|
||||
id: ctx.pendingReviewerRef.toString(),
|
||||
privilegeLevel: 'readOnly',
|
||||
source: 'invite',
|
||||
pendingReviewer: true,
|
||||
},
|
||||
{
|
||||
id: this.readOnlyTokenRef.toString(),
|
||||
id: ctx.readOnlyTokenRef.toString(),
|
||||
privilegeLevel: 'readOnly',
|
||||
source: 'token',
|
||||
},
|
||||
{
|
||||
id: this.readWriteTokenRef.toString(),
|
||||
id: ctx.readWriteTokenRef.toString(),
|
||||
privilegeLevel: 'readAndWrite',
|
||||
source: 'token',
|
||||
},
|
||||
{
|
||||
id: this.reviewer1Ref.toString(),
|
||||
id: ctx.reviewer1Ref.toString(),
|
||||
privilegeLevel: 'review',
|
||||
source: 'invite',
|
||||
},
|
||||
{
|
||||
id: this.reviewer2Ref.toString(),
|
||||
id: ctx.reviewer2Ref.toString(),
|
||||
privilegeLevel: 'review',
|
||||
source: 'invite',
|
||||
},
|
||||
@@ -149,14 +172,14 @@ describe('CollaboratorsGetter', function () {
|
||||
})
|
||||
|
||||
describe('with a missing project', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectGetter.promises.getProject.resolves(null)
|
||||
beforeEach(function (ctx) {
|
||||
ctx.ProjectGetter.promises.getProject.resolves(null)
|
||||
})
|
||||
|
||||
it('should return a NotFoundError', async function () {
|
||||
it('should return a NotFoundError', async function (ctx) {
|
||||
await expect(
|
||||
this.CollaboratorsGetter.promises.getMemberIdsWithPrivilegeLevels(
|
||||
this.project._id
|
||||
ctx.CollaboratorsGetter.promises.getMemberIdsWithPrivilegeLevels(
|
||||
ctx.project._id
|
||||
)
|
||||
).to.be.rejectedWith(Errors.NotFoundError)
|
||||
})
|
||||
@@ -164,89 +187,89 @@ describe('CollaboratorsGetter', function () {
|
||||
})
|
||||
|
||||
describe('getMemberIds', function () {
|
||||
it('should return the ids', async function () {
|
||||
const memberIds = await this.CollaboratorsGetter.promises.getMemberIds(
|
||||
this.project._id
|
||||
it('should return the ids', async function (ctx) {
|
||||
const memberIds = await ctx.CollaboratorsGetter.promises.getMemberIds(
|
||||
ctx.project._id
|
||||
)
|
||||
expect(memberIds).to.have.members([
|
||||
this.ownerRef.toString(),
|
||||
this.readOnlyRef1.toString(),
|
||||
this.readOnlyRef2.toString(),
|
||||
this.readWriteRef1.toString(),
|
||||
this.readWriteRef2.toString(),
|
||||
this.pendingEditorRef.toString(),
|
||||
this.pendingReviewerRef.toString(),
|
||||
this.readWriteTokenRef.toString(),
|
||||
this.readOnlyTokenRef.toString(),
|
||||
this.reviewer1Ref.toString(),
|
||||
this.reviewer2Ref.toString(),
|
||||
ctx.ownerRef.toString(),
|
||||
ctx.readOnlyRef1.toString(),
|
||||
ctx.readOnlyRef2.toString(),
|
||||
ctx.readWriteRef1.toString(),
|
||||
ctx.readWriteRef2.toString(),
|
||||
ctx.pendingEditorRef.toString(),
|
||||
ctx.pendingReviewerRef.toString(),
|
||||
ctx.readWriteTokenRef.toString(),
|
||||
ctx.readOnlyTokenRef.toString(),
|
||||
ctx.reviewer1Ref.toString(),
|
||||
ctx.reviewer2Ref.toString(),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInvitedMemberIds', function () {
|
||||
it('should return the invited ids', async function () {
|
||||
it('should return the invited ids', async function (ctx) {
|
||||
const memberIds =
|
||||
await this.CollaboratorsGetter.promises.getInvitedMemberIds(
|
||||
this.project._id
|
||||
await ctx.CollaboratorsGetter.promises.getInvitedMemberIds(
|
||||
ctx.project._id
|
||||
)
|
||||
expect(memberIds).to.have.members([
|
||||
this.ownerRef.toString(),
|
||||
this.readOnlyRef1.toString(),
|
||||
this.readOnlyRef2.toString(),
|
||||
this.readWriteRef1.toString(),
|
||||
this.readWriteRef2.toString(),
|
||||
this.pendingEditorRef.toString(),
|
||||
this.pendingReviewerRef.toString(),
|
||||
this.reviewer1Ref.toString(),
|
||||
this.reviewer2Ref.toString(),
|
||||
ctx.ownerRef.toString(),
|
||||
ctx.readOnlyRef1.toString(),
|
||||
ctx.readOnlyRef2.toString(),
|
||||
ctx.readWriteRef1.toString(),
|
||||
ctx.readWriteRef2.toString(),
|
||||
ctx.pendingEditorRef.toString(),
|
||||
ctx.pendingReviewerRef.toString(),
|
||||
ctx.reviewer1Ref.toString(),
|
||||
ctx.reviewer2Ref.toString(),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getMemberIdPrivilegeLevel', function () {
|
||||
it('should return the privilege level if it exists', async function () {
|
||||
it('should return the privilege level if it exists', async function (ctx) {
|
||||
const level =
|
||||
await this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
this.readOnlyRef1,
|
||||
this.project._id
|
||||
await ctx.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
ctx.readOnlyRef1,
|
||||
ctx.project._id
|
||||
)
|
||||
expect(level).to.equal('readOnly')
|
||||
})
|
||||
|
||||
it('should return review privilege level', async function () {
|
||||
it('should return review privilege level', async function (ctx) {
|
||||
const level =
|
||||
await this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
this.reviewer1Ref,
|
||||
this.project._id
|
||||
await ctx.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
ctx.reviewer1Ref,
|
||||
ctx.project._id
|
||||
)
|
||||
expect(level).to.equal('review')
|
||||
})
|
||||
|
||||
it('should return false if the member has no privilege level', async function () {
|
||||
it('should return false if the member has no privilege level', async function (ctx) {
|
||||
const level =
|
||||
await this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
this.nonMemberRef,
|
||||
this.project._id
|
||||
await ctx.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
ctx.nonMemberRef,
|
||||
ctx.project._id
|
||||
)
|
||||
expect(level).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return review privilege level when user is both reviewer and token member', async function () {
|
||||
it('should return review privilege level when user is both reviewer and token member', async function (ctx) {
|
||||
const userWhoIsBothReviewerAndToken = new ObjectId()
|
||||
|
||||
const projectWithDuplicateUser = {
|
||||
...this.project,
|
||||
...ctx.project,
|
||||
reviewer_refs: [userWhoIsBothReviewerAndToken],
|
||||
tokenAccessReadAndWrite_refs: [userWhoIsBothReviewerAndToken],
|
||||
}
|
||||
|
||||
this.ProjectGetter.promises.getProject.resolves(projectWithDuplicateUser)
|
||||
ctx.ProjectGetter.promises.getProject.resolves(projectWithDuplicateUser)
|
||||
|
||||
const level =
|
||||
await this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
await ctx.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
userWhoIsBothReviewerAndToken,
|
||||
this.project._id
|
||||
ctx.project._id
|
||||
)
|
||||
|
||||
expect(level).to.equal('review')
|
||||
@@ -255,20 +278,20 @@ describe('CollaboratorsGetter', function () {
|
||||
|
||||
describe('isUserInvitedMemberOfProject', function () {
|
||||
describe('when user is a member of the project', function () {
|
||||
it('should return true and the privilegeLevel', async function () {
|
||||
it('should return true and the privilegeLevel', async function (ctx) {
|
||||
const isMember =
|
||||
await this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject(
|
||||
this.readOnlyRef1
|
||||
await ctx.CollaboratorsGetter.promises.isUserInvitedMemberOfProject(
|
||||
ctx.readOnlyRef1
|
||||
)
|
||||
expect(isMember).to.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when user is not a member of the project', function () {
|
||||
it('should return false', async function () {
|
||||
it('should return false', async function (ctx) {
|
||||
const isMember =
|
||||
await this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject(
|
||||
this.nonMemberRef
|
||||
await ctx.CollaboratorsGetter.promises.isUserInvitedMemberOfProject(
|
||||
ctx.nonMemberRef
|
||||
)
|
||||
expect(isMember).to.equal(false)
|
||||
})
|
||||
@@ -277,30 +300,30 @@ describe('CollaboratorsGetter', function () {
|
||||
|
||||
describe('isUserInvitedReadWriteMemberOfProject', function () {
|
||||
describe('when user is a read write member of the project', function () {
|
||||
it('should return true', async function () {
|
||||
it('should return true', async function (ctx) {
|
||||
const isMember =
|
||||
await this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject(
|
||||
this.readWriteRef1
|
||||
await ctx.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject(
|
||||
ctx.readWriteRef1
|
||||
)
|
||||
expect(isMember).to.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when user is a read only member of the project', function () {
|
||||
it('should return false', async function () {
|
||||
it('should return false', async function (ctx) {
|
||||
const isMember =
|
||||
await this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject(
|
||||
this.readOnlyRef1
|
||||
await ctx.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject(
|
||||
ctx.readOnlyRef1
|
||||
)
|
||||
expect(isMember).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when user is not a member of the project', function () {
|
||||
it('should return false', async function () {
|
||||
it('should return false', async function (ctx) {
|
||||
const isMember =
|
||||
await this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject(
|
||||
this.nonMemberRef
|
||||
await ctx.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject(
|
||||
ctx.nonMemberRef
|
||||
)
|
||||
expect(isMember).to.equal(false)
|
||||
})
|
||||
@@ -308,42 +331,42 @@ describe('CollaboratorsGetter', function () {
|
||||
})
|
||||
|
||||
describe('getProjectsUserIsMemberOf', function () {
|
||||
beforeEach(function () {
|
||||
this.fields = 'mock fields'
|
||||
this.ProjectMock.expects('find')
|
||||
.withArgs({ collaberator_refs: this.userId }, this.fields)
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fields = 'mock fields'
|
||||
ctx.ProjectMock.expects('find')
|
||||
.withArgs({ collaberator_refs: ctx.userId }, ctx.fields)
|
||||
.chain('exec')
|
||||
.resolves(['mock-read-write-project-1', 'mock-read-write-project-2'])
|
||||
|
||||
this.ProjectMock.expects('find')
|
||||
.withArgs({ readOnly_refs: this.userId }, this.fields)
|
||||
ctx.ProjectMock.expects('find')
|
||||
.withArgs({ readOnly_refs: ctx.userId }, ctx.fields)
|
||||
.chain('exec')
|
||||
.resolves(['mock-read-only-project-1', 'mock-read-only-project-2'])
|
||||
|
||||
this.ProjectMock.expects('find')
|
||||
.withArgs({ reviewer_refs: this.userId }, this.fields)
|
||||
ctx.ProjectMock.expects('find')
|
||||
.withArgs({ reviewer_refs: ctx.userId }, ctx.fields)
|
||||
.chain('exec')
|
||||
.resolves(['mock-review-project-1', 'mock-review-project-2'])
|
||||
this.ProjectMock.expects('find')
|
||||
ctx.ProjectMock.expects('find')
|
||||
.withArgs(
|
||||
{
|
||||
tokenAccessReadAndWrite_refs: this.userId,
|
||||
tokenAccessReadAndWrite_refs: ctx.userId,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
this.fields
|
||||
ctx.fields
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves([
|
||||
'mock-token-read-write-project-1',
|
||||
'mock-token-read-write-project-2',
|
||||
])
|
||||
this.ProjectMock.expects('find')
|
||||
ctx.ProjectMock.expects('find')
|
||||
.withArgs(
|
||||
{
|
||||
tokenAccessReadOnly_refs: this.userId,
|
||||
tokenAccessReadOnly_refs: ctx.userId,
|
||||
publicAccesLevel: 'tokenBased',
|
||||
},
|
||||
this.fields
|
||||
ctx.fields
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves([
|
||||
@@ -352,11 +375,11 @@ describe('CollaboratorsGetter', function () {
|
||||
])
|
||||
})
|
||||
|
||||
it('should call the callback with the projects', async function () {
|
||||
it('should call the callback with the projects', async function (ctx) {
|
||||
const projects =
|
||||
await this.CollaboratorsGetter.promises.getProjectsUserIsMemberOf(
|
||||
this.userId,
|
||||
this.fields
|
||||
await ctx.CollaboratorsGetter.promises.getProjectsUserIsMemberOf(
|
||||
ctx.userId,
|
||||
ctx.fields
|
||||
)
|
||||
expect(projects).to.deep.equal({
|
||||
readAndWrite: [
|
||||
@@ -378,101 +401,98 @@ describe('CollaboratorsGetter', function () {
|
||||
})
|
||||
|
||||
describe('getAllInvitedMembers', function () {
|
||||
beforeEach(async function () {
|
||||
this.owningUser = {
|
||||
_id: this.ownerRef,
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.owningUser = {
|
||||
_id: ctx.ownerRef,
|
||||
email: 'owner@example.com',
|
||||
features: { a: 1 },
|
||||
}
|
||||
this.readWriteUser = {
|
||||
_id: this.readWriteRef1,
|
||||
ctx.readWriteUser = {
|
||||
_id: ctx.readWriteRef1,
|
||||
email: 'readwrite@example.com',
|
||||
}
|
||||
this.reviewUser = {
|
||||
_id: this.reviewer1Ref,
|
||||
ctx.reviewUser = {
|
||||
_id: ctx.reviewer1Ref,
|
||||
email: 'review@example.com',
|
||||
}
|
||||
this.members = [
|
||||
{ user: this.owningUser, privilegeLevel: 'owner' },
|
||||
{ user: this.readWriteUser, privilegeLevel: 'readAndWrite' },
|
||||
{ user: this.reviewUser, privilegeLevel: 'review' },
|
||||
ctx.members = [
|
||||
{ user: ctx.owningUser, privilegeLevel: 'owner' },
|
||||
{ user: ctx.readWriteUser, privilegeLevel: 'readAndWrite' },
|
||||
{ user: ctx.reviewUser, privilegeLevel: 'review' },
|
||||
]
|
||||
this.memberViews = [
|
||||
{ _id: this.readWriteUser._id, email: this.readWriteUser.email },
|
||||
{ _id: this.reviewUser._id, email: this.reviewUser.email },
|
||||
ctx.memberViews = [
|
||||
{ _id: ctx.readWriteUser._id, email: ctx.readWriteUser.email },
|
||||
{ _id: ctx.reviewUser._id, email: ctx.reviewUser.email },
|
||||
]
|
||||
this.UserGetter.promises.getUsers.resolves([
|
||||
this.owningUser,
|
||||
this.readWriteUser,
|
||||
this.reviewUser,
|
||||
ctx.UserGetter.promises.getUsers.resolves([
|
||||
ctx.owningUser,
|
||||
ctx.readWriteUser,
|
||||
ctx.reviewUser,
|
||||
])
|
||||
this.ProjectEditorHandler.buildUserModelView
|
||||
.withArgs(this.members[1])
|
||||
.returns(this.memberViews[0])
|
||||
this.ProjectEditorHandler.buildUserModelView
|
||||
.withArgs(this.members[2])
|
||||
.returns(this.memberViews[1])
|
||||
this.result =
|
||||
await this.CollaboratorsGetter.promises.getAllInvitedMembers(
|
||||
this.project._id
|
||||
)
|
||||
ctx.ProjectEditorHandler.buildUserModelView
|
||||
.withArgs(ctx.members[1])
|
||||
.returns(ctx.memberViews[0])
|
||||
ctx.ProjectEditorHandler.buildUserModelView
|
||||
.withArgs(ctx.members[2])
|
||||
.returns(ctx.memberViews[1])
|
||||
ctx.result = await ctx.CollaboratorsGetter.promises.getAllInvitedMembers(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
|
||||
it('should produce a list of members', function () {
|
||||
expect(this.result).to.deep.equal(this.memberViews)
|
||||
it('should produce a list of members', function (ctx) {
|
||||
expect(ctx.result).to.deep.equal(ctx.memberViews)
|
||||
})
|
||||
|
||||
it('should call ProjectEditorHandler.buildUserModelView', function () {
|
||||
expect(this.ProjectEditorHandler.buildUserModelView).to.have.been
|
||||
it('should call ProjectEditorHandler.buildUserModelView', function (ctx) {
|
||||
expect(ctx.ProjectEditorHandler.buildUserModelView).to.have.been
|
||||
.calledTwice
|
||||
expect(
|
||||
this.ProjectEditorHandler.buildUserModelView
|
||||
).to.have.been.calledWith(this.members[1])
|
||||
ctx.ProjectEditorHandler.buildUserModelView
|
||||
).to.have.been.calledWith(ctx.members[1])
|
||||
expect(
|
||||
this.ProjectEditorHandler.buildUserModelView
|
||||
).to.have.been.calledWith(this.members[2])
|
||||
ctx.ProjectEditorHandler.buildUserModelView
|
||||
).to.have.been.calledWith(ctx.members[2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('userIsTokenMember', function () {
|
||||
it('should return true when the project is found', async function () {
|
||||
this.ProjectMock.expects('findOne').chain('exec').resolves(this.project)
|
||||
const isMember =
|
||||
await this.CollaboratorsGetter.promises.userIsTokenMember(
|
||||
this.userId,
|
||||
this.project._id
|
||||
)
|
||||
it('should return true when the project is found', async function (ctx) {
|
||||
ctx.ProjectMock.expects('findOne').chain('exec').resolves(ctx.project)
|
||||
const isMember = await ctx.CollaboratorsGetter.promises.userIsTokenMember(
|
||||
ctx.userId,
|
||||
ctx.project._id
|
||||
)
|
||||
expect(isMember).to.be.true
|
||||
})
|
||||
|
||||
it('should return false when the project is not found', async function () {
|
||||
this.ProjectMock.expects('findOne').chain('exec').resolves(null)
|
||||
const isMember =
|
||||
await this.CollaboratorsGetter.promises.userIsTokenMember(
|
||||
this.userId,
|
||||
this.project._id
|
||||
)
|
||||
it('should return false when the project is not found', async function (ctx) {
|
||||
ctx.ProjectMock.expects('findOne').chain('exec').resolves(null)
|
||||
const isMember = await ctx.CollaboratorsGetter.promises.userIsTokenMember(
|
||||
ctx.userId,
|
||||
ctx.project._id
|
||||
)
|
||||
expect(isMember).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('userIsReadWriteTokenMember', function () {
|
||||
it('should return true when the project is found', async function () {
|
||||
this.ProjectMock.expects('findOne').chain('exec').resolves(this.project)
|
||||
it('should return true when the project is found', async function (ctx) {
|
||||
ctx.ProjectMock.expects('findOne').chain('exec').resolves(ctx.project)
|
||||
const isMember =
|
||||
await this.CollaboratorsGetter.promises.userIsReadWriteTokenMember(
|
||||
this.userId,
|
||||
this.project._id
|
||||
await ctx.CollaboratorsGetter.promises.userIsReadWriteTokenMember(
|
||||
ctx.userId,
|
||||
ctx.project._id
|
||||
)
|
||||
expect(isMember).to.be.true
|
||||
})
|
||||
|
||||
it('should return false when the project is not found', async function () {
|
||||
this.ProjectMock.expects('findOne').chain('exec').resolves(null)
|
||||
it('should return false when the project is not found', async function (ctx) {
|
||||
ctx.ProjectMock.expects('findOne').chain('exec').resolves(null)
|
||||
const isMember =
|
||||
await this.CollaboratorsGetter.promises.userIsReadWriteTokenMember(
|
||||
this.userId,
|
||||
this.project._id
|
||||
await ctx.CollaboratorsGetter.promises.userIsReadWriteTokenMember(
|
||||
ctx.userId,
|
||||
ctx.project._id
|
||||
)
|
||||
expect(isMember).to.be.false
|
||||
})
|
||||
@@ -481,53 +501,53 @@ describe('CollaboratorsGetter', function () {
|
||||
describe('getPublicShareTokens', function () {
|
||||
const userMock = new ObjectId()
|
||||
|
||||
it('should return null when the project is not found', async function () {
|
||||
this.ProjectMock.expects('findOne').chain('exec').resolves(undefined)
|
||||
it('should return null when the project is not found', async function (ctx) {
|
||||
ctx.ProjectMock.expects('findOne').chain('exec').resolves(undefined)
|
||||
const tokens =
|
||||
await this.CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
await ctx.CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
userMock,
|
||||
this.project._id
|
||||
ctx.project._id
|
||||
)
|
||||
expect(tokens).to.be.null
|
||||
})
|
||||
|
||||
it('should return an empty object when the user is not owner or read-only collaborator', async function () {
|
||||
this.ProjectMock.expects('findOne').chain('exec').resolves(this.project)
|
||||
it('should return an empty object when the user is not owner or read-only collaborator', async function (ctx) {
|
||||
ctx.ProjectMock.expects('findOne').chain('exec').resolves(ctx.project)
|
||||
const tokens =
|
||||
await this.CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
await ctx.CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
userMock,
|
||||
this.project._id
|
||||
ctx.project._id
|
||||
)
|
||||
expect(tokens).to.deep.equal({})
|
||||
})
|
||||
|
||||
describe('when the user is a read-only token collaborator', function () {
|
||||
it('should return the read-only token', async function () {
|
||||
this.ProjectMock.expects('findOne')
|
||||
it('should return the read-only token', async function (ctx) {
|
||||
ctx.ProjectMock.expects('findOne')
|
||||
.chain('exec')
|
||||
.resolves({ hasTokenReadOnlyAccess: true, ...this.project })
|
||||
.resolves({ hasTokenReadOnlyAccess: true, ...ctx.project })
|
||||
|
||||
const tokens =
|
||||
await this.CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
await ctx.CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
userMock,
|
||||
this.project._id
|
||||
ctx.project._id
|
||||
)
|
||||
expect(tokens).to.deep.equal({ readOnly: tokens.readOnly })
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the user is the owner of the project', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectMock.expects('findOne')
|
||||
beforeEach(function (ctx) {
|
||||
ctx.ProjectMock.expects('findOne')
|
||||
.chain('exec')
|
||||
.resolves({ isOwner: true, ...this.project })
|
||||
.resolves({ isOwner: true, ...ctx.project })
|
||||
})
|
||||
|
||||
it('should return all the tokens', async function () {
|
||||
it('should return all the tokens', async function (ctx) {
|
||||
const tokens =
|
||||
await this.CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
await ctx.CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
userMock,
|
||||
this.project._id
|
||||
ctx.project._id
|
||||
)
|
||||
expect(tokens).to.deep.equal(tokens)
|
||||
})
|
||||
@@ -535,20 +555,20 @@ describe('CollaboratorsGetter', function () {
|
||||
})
|
||||
|
||||
describe('getInvitedEditCollaboratorCount', function () {
|
||||
it('should return the count of invited edit collaborators (readAndWrite, review)', async function () {
|
||||
it('should return the count of invited edit collaborators (readAndWrite, review)', async function (ctx) {
|
||||
const count =
|
||||
await this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
|
||||
this.project._id
|
||||
await ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
|
||||
ctx.project._id
|
||||
)
|
||||
expect(count).to.equal(4)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInvitedPendingEditorCount', function () {
|
||||
it('should return the count of pending editors and reviewers', async function () {
|
||||
it('should return the count of pending editors and reviewers', async function (ctx) {
|
||||
const count =
|
||||
await this.CollaboratorsGetter.promises.getInvitedPendingEditorCount(
|
||||
this.project._id
|
||||
await ctx.CollaboratorsGetter.promises.getInvitedPendingEditorCount(
|
||||
ctx.project._id
|
||||
)
|
||||
expect(count).to.equal(2)
|
||||
})
|
||||
@@ -556,11 +576,11 @@ describe('CollaboratorsGetter', function () {
|
||||
|
||||
describe('ProjectAccess', function () {
|
||||
describe('privilegeLevelForUser', function () {
|
||||
it('should return reviewer privilege when user is both reviewer and token member', function () {
|
||||
it('should return reviewer privilege when user is both reviewer and token member', function (ctx) {
|
||||
const userWhoIsBothReviewerAndToken = new ObjectId()
|
||||
|
||||
const projectWithDuplicateUser = {
|
||||
owner_ref: this.ownerRef,
|
||||
owner_ref: ctx.ownerRef,
|
||||
collaberator_refs: [],
|
||||
readOnly_refs: [],
|
||||
tokenAccessReadAndWrite_refs: [userWhoIsBothReviewerAndToken],
|
||||
@@ -571,7 +591,7 @@ describe('CollaboratorsGetter', function () {
|
||||
pendingReviewer_refs: [],
|
||||
}
|
||||
|
||||
const projectAccess = new this.CollaboratorsGetter.ProjectAccess(
|
||||
const projectAccess = new ctx.CollaboratorsGetter.ProjectAccess(
|
||||
projectWithDuplicateUser
|
||||
)
|
||||
const privilegeLevel = projectAccess.privilegeLevelForUser(
|
||||
@@ -581,11 +601,11 @@ describe('CollaboratorsGetter', function () {
|
||||
expect(privilegeLevel).to.equal('review')
|
||||
})
|
||||
|
||||
it('should return readOnly privilege when user is both readOnly and token readAndWrite member', function () {
|
||||
it('should return readOnly privilege when user is both readOnly and token readAndWrite member', function (ctx) {
|
||||
const userWhoIsBothReadOnlyAndTokenRW = new ObjectId()
|
||||
|
||||
const projectWithDuplicateUser = {
|
||||
owner_ref: this.ownerRef,
|
||||
owner_ref: ctx.ownerRef,
|
||||
collaberator_refs: [],
|
||||
readOnly_refs: [userWhoIsBothReadOnlyAndTokenRW],
|
||||
tokenAccessReadAndWrite_refs: [userWhoIsBothReadOnlyAndTokenRW],
|
||||
@@ -596,7 +616,7 @@ describe('CollaboratorsGetter', function () {
|
||||
pendingReviewer_refs: [],
|
||||
}
|
||||
|
||||
const projectAccess = new this.CollaboratorsGetter.ProjectAccess(
|
||||
const projectAccess = new ctx.CollaboratorsGetter.ProjectAccess(
|
||||
projectWithDuplicateUser
|
||||
)
|
||||
const privilegeLevel = projectAccess.privilegeLevelForUser(
|
||||
@@ -607,12 +627,12 @@ describe('CollaboratorsGetter', function () {
|
||||
expect(privilegeLevel).to.equal('readOnly')
|
||||
})
|
||||
|
||||
it('should return none for non-members', function () {
|
||||
const projectAccess = new this.CollaboratorsGetter.ProjectAccess(
|
||||
this.project
|
||||
it('should return none for non-members', function (ctx) {
|
||||
const projectAccess = new ctx.CollaboratorsGetter.ProjectAccess(
|
||||
ctx.project
|
||||
)
|
||||
const privilegeLevel = projectAccess.privilegeLevelForUser(
|
||||
this.nonMemberRef
|
||||
ctx.nonMemberRef
|
||||
)
|
||||
|
||||
expect(privilegeLevel).to.equal(false)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,9 +22,12 @@ describe('DocumentUpdaterController', function () {
|
||||
default: ctx.settings,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectLocator.mjs', () => ({
|
||||
default: ctx.ProjectLocator,
|
||||
}))
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectLocator.mjs',
|
||||
() => ({
|
||||
default: ctx.ProjectLocator,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler.mjs',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,9 +43,12 @@ describe('ProjectZipStreamManager', function () {
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/History/HistoryManager.mjs', () => ({
|
||||
default: (ctx.HistoryManager = {}),
|
||||
}))
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/History/HistoryManager.mjs',
|
||||
() => ({
|
||||
default: (ctx.HistoryManager = {}),
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
|
||||
default: (ctx.ProjectGetter = {}),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -168,9 +168,12 @@ describe('EditorHttpController', function () {
|
||||
vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
|
||||
vi.importActual('../../../../app/src/Features/Errors/Errors.js')
|
||||
)
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectDeleter.mjs', () => ({
|
||||
default: ctx.ProjectDeleter,
|
||||
}))
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectDeleter.mjs',
|
||||
() => ({
|
||||
default: ctx.ProjectDeleter,
|
||||
})
|
||||
)
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectGetter.mjs', () => ({
|
||||
default: ctx.ProjectGetter,
|
||||
}))
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
import { beforeEach, describe, it, vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const MODULE_PATH = '../../../../app/src/Features/FileStore/FileStoreHandler.js'
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/FileStore/FileStoreHandler.mjs'
|
||||
|
||||
describe('FileStoreHandler', function () {
|
||||
beforeEach(function () {
|
||||
this.fileSize = 999
|
||||
this.fs = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.fileSize = 999
|
||||
ctx.fs = {
|
||||
createReadStream: sinon.stub(),
|
||||
lstat: sinon.stub().callsArgWith(1, null, {
|
||||
isFile() {
|
||||
@@ -16,10 +16,10 @@ describe('FileStoreHandler', function () {
|
||||
isDirectory() {
|
||||
return false
|
||||
},
|
||||
size: this.fileSize,
|
||||
size: ctx.fileSize,
|
||||
}),
|
||||
}
|
||||
this.writeStream = {
|
||||
ctx.writeStream = {
|
||||
my: 'writeStream',
|
||||
on(type, fn) {
|
||||
if (type === 'response') {
|
||||
@@ -27,25 +27,24 @@ describe('FileStoreHandler', function () {
|
||||
}
|
||||
},
|
||||
}
|
||||
this.readStream = { my: 'readStream', on: sinon.stub() }
|
||||
this.request = sinon.stub()
|
||||
this.request.head = sinon.stub()
|
||||
this.filestoreUrl = 'http://filestore.overleaf.test'
|
||||
this.settings = {
|
||||
apis: { filestore: { url: this.filestoreUrl } },
|
||||
ctx.readStream = { my: 'readStream', on: sinon.stub() }
|
||||
ctx.request = sinon.stub()
|
||||
ctx.request.head = sinon.stub()
|
||||
ctx.filestoreUrl = 'http://filestore.overleaf.test'
|
||||
ctx.settings = {
|
||||
apis: { filestore: { url: ctx.filestoreUrl } },
|
||||
}
|
||||
this.hashValue = '0123456789'
|
||||
this.fileArgs = { name: 'upload-filename' }
|
||||
this.fileId = 'file_id_here'
|
||||
this.projectId = '1312312312'
|
||||
this.historyId = 123
|
||||
this.hashValue = '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'
|
||||
this.fsPath = 'uploads/myfile.eps'
|
||||
this.getFileUrl = (projectId, fileId) =>
|
||||
`${this.filestoreUrl}/project/${projectId}/file/${fileId}`
|
||||
this.getProjectUrl = projectId =>
|
||||
`${this.filestoreUrl}/project/${projectId}`
|
||||
this.FileModel = class File {
|
||||
ctx.hashValue = '0123456789'
|
||||
ctx.fileArgs = { name: 'upload-filename' }
|
||||
ctx.fileId = 'file_id_here'
|
||||
ctx.projectId = '1312312312'
|
||||
ctx.historyId = 123
|
||||
ctx.hashValue = '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'
|
||||
ctx.fsPath = 'uploads/myfile.eps'
|
||||
ctx.getFileUrl = (projectId, fileId) =>
|
||||
`${ctx.filestoreUrl}/project/${projectId}/file/${fileId}`
|
||||
ctx.getProjectUrl = projectId => `${ctx.filestoreUrl}/project/${projectId}`
|
||||
ctx.FileModel = class File {
|
||||
constructor(options) {
|
||||
;({ name: this.name, hash: this.hash } = options)
|
||||
this._id = 'file_id_here'
|
||||
@@ -55,53 +54,75 @@ describe('FileStoreHandler', function () {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.FileHashManager = {
|
||||
computeHash: sinon.stub().callsArgWith(1, null, this.hashValue),
|
||||
ctx.FileHashManager = {
|
||||
computeHash: sinon.stub().callsArgWith(1, null, ctx.hashValue),
|
||||
}
|
||||
this.HistoryManager = {
|
||||
ctx.HistoryManager = {
|
||||
uploadBlobFromDisk: sinon.stub().callsArg(4),
|
||||
}
|
||||
this.ProjectDetailsHandler = {
|
||||
ctx.ProjectDetailsHandler = {
|
||||
getDetails: sinon.stub().callsArgWith(1, null, {
|
||||
overleaf: { history: { id: this.historyId } },
|
||||
overleaf: { history: { id: ctx.historyId } },
|
||||
}),
|
||||
}
|
||||
|
||||
this.Features = {
|
||||
ctx.Features = {
|
||||
hasFeature: sinon.stub(),
|
||||
}
|
||||
|
||||
this.Modules = {
|
||||
ctx.Modules = {
|
||||
hooks: {
|
||||
fire: sinon.stub().callsArgWith(2, null),
|
||||
},
|
||||
}
|
||||
|
||||
this.handler = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'@overleaf/settings': this.settings,
|
||||
request: this.request,
|
||||
'../History/HistoryManager': this.HistoryManager,
|
||||
'../Project/ProjectDetailsHandler': this.ProjectDetailsHandler,
|
||||
'./FileHashManager': this.FileHashManager,
|
||||
'../../infrastructure/Features': this.Features,
|
||||
'../../infrastructure/Modules': this.Modules,
|
||||
// FIXME: need to stub File object here
|
||||
'../../models/File': {
|
||||
File: this.FileModel,
|
||||
},
|
||||
fs: this.fs,
|
||||
},
|
||||
})
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: ctx.settings,
|
||||
}))
|
||||
|
||||
vi.doMock('request', () => ({
|
||||
default: ctx.request,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/History/HistoryManager', () => ({
|
||||
default: ctx.HistoryManager,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectDetailsHandler',
|
||||
() => ({
|
||||
default: ctx.ProjectDetailsHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/FileStore/FileHashManager', () => ({
|
||||
default: ctx.FileHashManager,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/Features', () => ({
|
||||
default: ctx.Features,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/Modules', () => ({
|
||||
default: ctx.Modules,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/models/File', () => ({
|
||||
File: ctx.FileModel,
|
||||
}))
|
||||
|
||||
vi.doMock('node:fs', () => ({ default: ctx.fs }))
|
||||
|
||||
ctx.handler = (await import(MODULE_PATH)).default
|
||||
})
|
||||
|
||||
describe('uploadFileFromDisk', function () {
|
||||
beforeEach(function () {
|
||||
this.request.returns(this.writeStream)
|
||||
beforeEach(function (ctx) {
|
||||
ctx.request.returns(ctx.writeStream)
|
||||
})
|
||||
|
||||
it('should get the project details', async function () {
|
||||
this.fs.createReadStream.returns({
|
||||
it('should get the project details', async function (ctx) {
|
||||
ctx.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
@@ -109,18 +130,18 @@ describe('FileStoreHandler', function () {
|
||||
}
|
||||
},
|
||||
})
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
this.ProjectDetailsHandler.getDetails
|
||||
.calledWith(this.projectId)
|
||||
ctx.ProjectDetailsHandler.getDetails
|
||||
.calledWith(ctx.projectId)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should compute the file hash', async function () {
|
||||
this.fs.createReadStream.returns({
|
||||
it('should compute the file hash', async function (ctx) {
|
||||
ctx.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
@@ -128,18 +149,16 @@ describe('FileStoreHandler', function () {
|
||||
}
|
||||
},
|
||||
})
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
this.FileHashManager.computeHash
|
||||
.calledWith(this.fsPath)
|
||||
.should.equal(true)
|
||||
ctx.FileHashManager.computeHash.calledWith(ctx.fsPath).should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the preUploadFile hook', async function () {
|
||||
this.fs.createReadStream.returns({
|
||||
it('should call the preUploadFile hook', async function (ctx) {
|
||||
ctx.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
@@ -147,24 +166,24 @@ describe('FileStoreHandler', function () {
|
||||
}
|
||||
},
|
||||
})
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
this.Modules.hooks.fire
|
||||
ctx.Modules.hooks.fire
|
||||
.calledWith('preUploadFile', {
|
||||
projectId: this.projectId,
|
||||
historyId: this.historyId,
|
||||
fileArgs: this.fileArgs,
|
||||
fsPath: this.fsPath,
|
||||
size: this.fileSize,
|
||||
projectId: ctx.projectId,
|
||||
historyId: ctx.historyId,
|
||||
fileArgs: ctx.fileArgs,
|
||||
fsPath: ctx.fsPath,
|
||||
size: ctx.fileSize,
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should upload the file to the history store as a blob', async function () {
|
||||
this.fs.createReadStream.returns({
|
||||
it('should upload the file to the history store as a blob', async function (ctx) {
|
||||
ctx.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
@@ -172,37 +191,37 @@ describe('FileStoreHandler', function () {
|
||||
}
|
||||
},
|
||||
})
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
this.HistoryManager.uploadBlobFromDisk
|
||||
.calledWith(this.historyId, this.hashValue, this.fileSize, this.fsPath)
|
||||
ctx.HistoryManager.uploadBlobFromDisk
|
||||
.calledWith(ctx.historyId, ctx.hashValue, ctx.fileSize, ctx.fsPath)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should not open file handle', async function () {
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
it('should not open file handle', async function (ctx) {
|
||||
await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
expect(this.fs.createReadStream).to.not.have.been.called
|
||||
expect(ctx.fs.createReadStream).to.not.have.been.called
|
||||
})
|
||||
|
||||
it('should not talk to filestore', async function () {
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
it('should not talk to filestore', async function (ctx) {
|
||||
await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
|
||||
expect(this.request).to.not.have.been.called
|
||||
expect(ctx.request).to.not.have.been.called
|
||||
})
|
||||
|
||||
it('should call the postUploadFile hook', async function () {
|
||||
this.fs.createReadStream.returns({
|
||||
it('should call the postUploadFile hook', async function (ctx) {
|
||||
ctx.fs.createReadStream.returns({
|
||||
pipe() {},
|
||||
on(type, cb) {
|
||||
if (type === 'open') {
|
||||
@@ -210,33 +229,33 @@ describe('FileStoreHandler', function () {
|
||||
}
|
||||
},
|
||||
})
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
this.Modules.hooks.fire
|
||||
ctx.Modules.hooks.fire
|
||||
.calledWith('postUploadFile', {
|
||||
projectId: this.projectId,
|
||||
fileRef: sinon.match.instanceOf(this.FileModel),
|
||||
size: this.fileSize,
|
||||
projectId: ctx.projectId,
|
||||
fileRef: sinon.match.instanceOf(ctx.FileModel),
|
||||
size: ctx.fileSize,
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve with the url and fileRef', async function () {
|
||||
const { fileRef } = await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
it('should resolve with the url and fileRef', async function (ctx) {
|
||||
const { fileRef } = await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
expect(fileRef._id).to.equal(this.fileId)
|
||||
expect(fileRef.hash).to.equal(this.hashValue)
|
||||
expect(fileRef._id).to.equal(ctx.fileId)
|
||||
expect(fileRef.hash).to.equal(ctx.hashValue)
|
||||
})
|
||||
|
||||
describe('symlink', function () {
|
||||
it('should not read file if it is symlink', async function () {
|
||||
this.fs.lstat = sinon.stub().callsArgWith(1, null, {
|
||||
it('should not read file if it is symlink', async function (ctx) {
|
||||
ctx.fs.lstat = sinon.stub().callsArgWith(1, null, {
|
||||
isFile() {
|
||||
return false
|
||||
},
|
||||
@@ -248,10 +267,10 @@ describe('FileStoreHandler', function () {
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
} catch (err) {
|
||||
error = err
|
||||
@@ -259,18 +278,18 @@ describe('FileStoreHandler', function () {
|
||||
|
||||
expect(error).to.exist
|
||||
|
||||
this.fs.createReadStream.called.should.equal(false)
|
||||
ctx.fs.createReadStream.called.should.equal(false)
|
||||
})
|
||||
|
||||
it('should not read file stat returns nothing', async function () {
|
||||
this.fs.lstat = sinon.stub().callsArgWith(1, null, null)
|
||||
it('should not read file stat returns nothing', async function (ctx) {
|
||||
ctx.fs.lstat = sinon.stub().callsArgWith(1, null, null)
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.handler.promises.uploadFileFromDisk(
|
||||
this.projectId,
|
||||
this.fileArgs,
|
||||
this.fsPath
|
||||
await ctx.handler.promises.uploadFileFromDisk(
|
||||
ctx.projectId,
|
||||
ctx.fileArgs,
|
||||
ctx.fsPath
|
||||
)
|
||||
} catch (err) {
|
||||
error = err
|
||||
@@ -278,7 +297,7 @@ describe('FileStoreHandler', function () {
|
||||
|
||||
expect(error).to.exist
|
||||
|
||||
this.fs.createReadStream.called.should.equal(false)
|
||||
ctx.fs.createReadStream.called.should.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
const { expect } = require('chai')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const MockRequest = require('../helpers/MockRequest')
|
||||
const MockResponse = require('../helpers/MockResponse')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import MockRequest from '../helpers/MockRequest.js'
|
||||
import MockResponse from '../helpers/MockResponse.js'
|
||||
|
||||
const modulePath =
|
||||
'../../../../app/src/Features/Helpers/AdminAuthorizationHelper'
|
||||
|
||||
describe('AdminAuthorizationHelper', function () {
|
||||
beforeEach(function () {
|
||||
this.fireHook = sinon.stub().resolves([])
|
||||
this.settings = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.fireHook = sinon.stub().resolves([])
|
||||
ctx.settings = {
|
||||
adminPrivilegeAvailable: true,
|
||||
adminUrl: 'https://admin.overleaf.com',
|
||||
adminRolesEnabled: true,
|
||||
}
|
||||
this.AdminAuthorizationHelper = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'@overleaf/settings': this.settings,
|
||||
'../../infrastructure/Modules': {
|
||||
promises: {
|
||||
hooks: {
|
||||
fire: this.fireHook,
|
||||
},
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: ctx.settings,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/Modules', () => ({
|
||||
default: {
|
||||
promises: {
|
||||
hooks: {
|
||||
fire: ctx.fireHook,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
ctx.AdminAuthorizationHelper = (await import(modulePath)).default
|
||||
})
|
||||
describe('getAdminCapabilities', function () {
|
||||
describe('when modules return capabilities', function () {
|
||||
@@ -34,9 +37,9 @@ describe('AdminAuthorizationHelper', function () {
|
||||
const module1Capabilities = ['capability1', 'capability2']
|
||||
const module2Capabilities = ['capability2', 'capability3']
|
||||
|
||||
beforeEach(async function () {
|
||||
this.fireHook.resolves([module1Capabilities, module2Capabilities])
|
||||
result = await this.AdminAuthorizationHelper.getAdminCapabilities({})
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.fireHook.resolves([module1Capabilities, module2Capabilities])
|
||||
result = await ctx.AdminAuthorizationHelper.getAdminCapabilities({})
|
||||
})
|
||||
it('returns true for adminCapabilitiesAvailable', async function () {
|
||||
expect(result.adminCapabilitiesAvailable).to.be.true
|
||||
@@ -49,8 +52,8 @@ describe('AdminAuthorizationHelper', function () {
|
||||
})
|
||||
describe('when no module returns capabilities', function () {
|
||||
let result
|
||||
beforeEach(async function () {
|
||||
result = await this.AdminAuthorizationHelper.getAdminCapabilities({})
|
||||
beforeEach(async function (ctx) {
|
||||
result = await ctx.AdminAuthorizationHelper.getAdminCapabilities({})
|
||||
})
|
||||
|
||||
it('returns false for adminCapabilitiesAvailable', function () {
|
||||
@@ -64,204 +67,203 @@ describe('AdminAuthorizationHelper', function () {
|
||||
describe('useAdminCapabilities', function () {
|
||||
describe('when admin capabilities are not available', function () {
|
||||
describe('user is null', function () {
|
||||
beforeEach(async function () {
|
||||
this.req = new MockRequest()
|
||||
this.res = new MockResponse()
|
||||
this.next = sinon.stub()
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.req = new MockRequest()
|
||||
ctx.res = new MockResponse()
|
||||
ctx.next = sinon.stub()
|
||||
|
||||
this.req.session = {
|
||||
ctx.req.session = {
|
||||
user: null,
|
||||
}
|
||||
|
||||
await this.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
this.req,
|
||||
this.res,
|
||||
this.next
|
||||
await ctx.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
ctx.next
|
||||
)
|
||||
})
|
||||
it('does not define adminCapabilitiesAvailable on req', function () {
|
||||
expect(this.req).not.to.have.property('adminCapabilitiesAvailable')
|
||||
it('does not define adminCapabilitiesAvailable on req', function (ctx) {
|
||||
expect(ctx.req).not.to.have.property('adminCapabilitiesAvailable')
|
||||
})
|
||||
it('defines adminCapabilities as an empty array on req', function () {
|
||||
expect(this.req).to.have.property('adminCapabilities')
|
||||
expect(this.req.adminCapabilities).to.be.an('array')
|
||||
expect(this.req.adminCapabilities).to.be.empty
|
||||
it('defines adminCapabilities as an empty array on req', function (ctx) {
|
||||
expect(ctx.req).to.have.property('adminCapabilities')
|
||||
expect(ctx.req.adminCapabilities).to.be.an('array')
|
||||
expect(ctx.req.adminCapabilities).to.be.empty
|
||||
})
|
||||
})
|
||||
describe('user is not an admin', function () {
|
||||
beforeEach(async function () {
|
||||
this.req = new MockRequest()
|
||||
this.res = new MockResponse()
|
||||
this.next = sinon.stub()
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.req = new MockRequest()
|
||||
ctx.res = new MockResponse()
|
||||
ctx.next = sinon.stub()
|
||||
|
||||
this.user = {
|
||||
ctx.user = {
|
||||
isAdmin: false,
|
||||
}
|
||||
|
||||
this.req.session = {
|
||||
user: this.user,
|
||||
ctx.req.session = {
|
||||
user: ctx.user,
|
||||
}
|
||||
|
||||
await this.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
this.req,
|
||||
this.res,
|
||||
this.next
|
||||
await ctx.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
ctx.next
|
||||
)
|
||||
})
|
||||
it('does not define adminCapabilitiesAvailable on req', function () {
|
||||
expect(this.req).not.to.have.property('adminCapabilitiesAvailable')
|
||||
it('does not define adminCapabilitiesAvailable on req', function (ctx) {
|
||||
expect(ctx.req).not.to.have.property('adminCapabilitiesAvailable')
|
||||
})
|
||||
it('defines adminCapabilities as an empty array on req', function () {
|
||||
expect(this.req).to.have.property('adminCapabilities')
|
||||
expect(this.req.adminCapabilities).to.be.an('array')
|
||||
expect(this.req.adminCapabilities).to.be.empty
|
||||
it('defines adminCapabilities as an empty array on req', function (ctx) {
|
||||
expect(ctx.req).to.have.property('adminCapabilities')
|
||||
expect(ctx.req.adminCapabilities).to.be.an('array')
|
||||
expect(ctx.req.adminCapabilities).to.be.empty
|
||||
})
|
||||
})
|
||||
describe('user is an admin', function () {
|
||||
beforeEach(async function () {
|
||||
this.req = new MockRequest()
|
||||
this.res = new MockResponse()
|
||||
this.next = sinon.stub()
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.req = new MockRequest()
|
||||
ctx.res = new MockResponse()
|
||||
ctx.next = sinon.stub()
|
||||
|
||||
this.user = {
|
||||
ctx.user = {
|
||||
isAdmin: true,
|
||||
}
|
||||
|
||||
this.req.session = {
|
||||
user: this.user,
|
||||
ctx.req.session = {
|
||||
user: ctx.user,
|
||||
}
|
||||
|
||||
await this.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
this.req,
|
||||
this.res,
|
||||
this.next
|
||||
await ctx.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
ctx.next
|
||||
)
|
||||
})
|
||||
|
||||
it('defines adminCapabilitiesAvailable as false on req', function () {
|
||||
expect(this.req).to.have.property('adminCapabilitiesAvailable', false)
|
||||
it('defines adminCapabilitiesAvailable as false on req', function (ctx) {
|
||||
expect(ctx.req).to.have.property('adminCapabilitiesAvailable', false)
|
||||
})
|
||||
|
||||
it('defines adminCapabilities as an empty array', function () {
|
||||
expect(this.req).to.have.property('adminCapabilities')
|
||||
expect(this.req.adminCapabilities).to.be.an('array')
|
||||
expect(this.req.adminCapabilities).to.be.empty
|
||||
it('defines adminCapabilities as an empty array', function (ctx) {
|
||||
expect(ctx.req).to.have.property('adminCapabilities')
|
||||
expect(ctx.req.adminCapabilities).to.be.an('array')
|
||||
expect(ctx.req.adminCapabilities).to.be.empty
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('when admin capabilities are available', function () {
|
||||
beforeEach(function () {
|
||||
this.fireHook.resolves(['capability1', 'capability2'])
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fireHook.resolves(['capability1', 'capability2'])
|
||||
})
|
||||
describe('user is not an admin', function () {
|
||||
beforeEach(async function () {
|
||||
this.req = new MockRequest()
|
||||
this.res = new MockResponse()
|
||||
this.next = sinon.stub()
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.req = new MockRequest()
|
||||
ctx.res = new MockResponse()
|
||||
ctx.next = sinon.stub()
|
||||
|
||||
this.user = {
|
||||
ctx.user = {
|
||||
isAdmin: false,
|
||||
}
|
||||
|
||||
this.req.session = {
|
||||
user: this.user,
|
||||
ctx.req.session = {
|
||||
user: ctx.user,
|
||||
}
|
||||
|
||||
await this.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
this.req,
|
||||
this.res,
|
||||
this.next
|
||||
await ctx.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
ctx.next
|
||||
)
|
||||
})
|
||||
it('does not define adminCapabilitiesAvailable on req', function () {
|
||||
expect(this.req).not.to.have.property('adminCapabilitiesAvailable')
|
||||
it('does not define adminCapabilitiesAvailable on req', function (ctx) {
|
||||
expect(ctx.req).not.to.have.property('adminCapabilitiesAvailable')
|
||||
})
|
||||
it('defines adminCapabilities as an empty array on req', function () {
|
||||
expect(this.req).to.have.property('adminCapabilities')
|
||||
expect(this.req.adminCapabilities).to.be.an('array')
|
||||
expect(this.req.adminCapabilities).to.be.empty
|
||||
it('defines adminCapabilities as an empty array on req', function (ctx) {
|
||||
expect(ctx.req).to.have.property('adminCapabilities')
|
||||
expect(ctx.req.adminCapabilities).to.be.an('array')
|
||||
expect(ctx.req.adminCapabilities).to.be.empty
|
||||
})
|
||||
})
|
||||
describe('user is an admin', function () {
|
||||
beforeEach(async function () {
|
||||
this.req = new MockRequest()
|
||||
this.res = new MockResponse()
|
||||
this.next = sinon.stub()
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.req = new MockRequest()
|
||||
ctx.res = new MockResponse()
|
||||
ctx.next = sinon.stub()
|
||||
|
||||
this.user = {
|
||||
ctx.user = {
|
||||
isAdmin: true,
|
||||
}
|
||||
|
||||
this.req.session = {
|
||||
user: this.user,
|
||||
ctx.req.session = {
|
||||
user: ctx.user,
|
||||
}
|
||||
|
||||
await this.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
this.req,
|
||||
this.res,
|
||||
this.next
|
||||
await ctx.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
ctx.next
|
||||
)
|
||||
})
|
||||
|
||||
it('defines adminCapabilitiesAvailable as true on req', function () {
|
||||
expect(this.req).to.have.property('adminCapabilitiesAvailable', true)
|
||||
it('defines adminCapabilitiesAvailable as true on req', function (ctx) {
|
||||
expect(ctx.req).to.have.property('adminCapabilitiesAvailable', true)
|
||||
})
|
||||
it('defines adminCapabilities with the capabilities returned from modules', function () {
|
||||
expect(this.req).to.have.property('adminCapabilities')
|
||||
expect(this.req.adminCapabilities).to.be.an('array')
|
||||
expect(this.req.adminCapabilities).to.include('capability1')
|
||||
expect(this.req.adminCapabilities).to.include('capability2')
|
||||
it('defines adminCapabilities with the capabilities returned from modules', function (ctx) {
|
||||
expect(ctx.req).to.have.property('adminCapabilities')
|
||||
expect(ctx.req.adminCapabilities).to.be.an('array')
|
||||
expect(ctx.req.adminCapabilities).to.include('capability1')
|
||||
expect(ctx.req.adminCapabilities).to.include('capability2')
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('when getting capabilities from modules throws an error', function () {
|
||||
beforeEach(async function () {
|
||||
this.fireHook.rejects(new Error('Module error'))
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.fireHook.rejects(new Error('Module error'))
|
||||
|
||||
this.req = new MockRequest()
|
||||
this.res = new MockResponse()
|
||||
this.next = sinon.stub()
|
||||
ctx.req = new MockRequest()
|
||||
ctx.res = new MockResponse()
|
||||
ctx.next = sinon.stub()
|
||||
|
||||
this.user = {
|
||||
ctx.user = {
|
||||
isAdmin: true,
|
||||
}
|
||||
|
||||
this.req.logger = {
|
||||
ctx.req.logger = {
|
||||
warn: sinon.stub(),
|
||||
}
|
||||
|
||||
this.req.session = {
|
||||
user: this.user,
|
||||
ctx.req.session = {
|
||||
user: ctx.user,
|
||||
}
|
||||
|
||||
await this.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
this.req,
|
||||
this.res,
|
||||
this.next
|
||||
await ctx.AdminAuthorizationHelper.useAdminCapabilities(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
ctx.next
|
||||
)
|
||||
})
|
||||
it('logs the error', function () {
|
||||
expect(this.logger.warn).to.have.been.calledWith(
|
||||
sinon.match.has('err', sinon.match.instanceOf(Error))
|
||||
)
|
||||
it('logs the error', function (ctx) {
|
||||
expect(ctx.logger.warn).toHaveBeenCalled()
|
||||
expect(ctx.logger.warn.mock.calls[0][0].err).toBeInstanceOf(Error)
|
||||
})
|
||||
it('defines adminCapabilitiesAvailable as true on req', function () {
|
||||
expect(this.req).to.have.property('adminCapabilitiesAvailable', true)
|
||||
it('defines adminCapabilitiesAvailable as true on req', function (ctx) {
|
||||
expect(ctx.req).to.have.property('adminCapabilitiesAvailable', true)
|
||||
})
|
||||
it('defines adminCapabilities as an empty array', function () {
|
||||
expect(this.req).to.have.property('adminCapabilities')
|
||||
expect(this.req.adminCapabilities).to.be.an('array')
|
||||
expect(this.req.adminCapabilities).to.be.empty
|
||||
it('defines adminCapabilities as an empty array', function (ctx) {
|
||||
expect(ctx.req).to.have.property('adminCapabilities')
|
||||
expect(ctx.req.adminCapabilities).to.be.an('array')
|
||||
expect(ctx.req.adminCapabilities).to.be.empty
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('useHasAdminCapability', function () {
|
||||
it('adds hasAdminCapability to res.locals', function () {
|
||||
it('adds hasAdminCapability to res.locals', function (ctx) {
|
||||
const req = new MockRequest()
|
||||
const res = new MockResponse()
|
||||
const next = sinon.stub()
|
||||
|
||||
this.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
ctx.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
|
||||
expect(res.locals).to.have.property('hasAdminCapability')
|
||||
expect(res.locals.hasAdminCapability).to.be.a('function')
|
||||
@@ -269,7 +271,7 @@ describe('AdminAuthorizationHelper', function () {
|
||||
|
||||
describe('when the user is not an admin', function () {
|
||||
describe('when req.adminCapabilitiesAvailable is true', function () {
|
||||
it('returns false for any capability', function () {
|
||||
it('returns false for any capability', function (ctx) {
|
||||
const req = new MockRequest()
|
||||
const res = new MockResponse()
|
||||
const next = sinon.stub()
|
||||
@@ -279,14 +281,14 @@ describe('AdminAuthorizationHelper', function () {
|
||||
|
||||
req.session.user = { isAdmin: false }
|
||||
|
||||
this.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
ctx.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
|
||||
expect(res.locals.hasAdminCapability('capability1')).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('when req.adminCapabilitiesAvailable is false', function () {
|
||||
it('returns false for any capability', function () {
|
||||
it('returns false for any capability', function (ctx) {
|
||||
const req = new MockRequest()
|
||||
const res = new MockResponse()
|
||||
const next = sinon.stub()
|
||||
@@ -296,21 +298,21 @@ describe('AdminAuthorizationHelper', function () {
|
||||
|
||||
req.session.user = { isAdmin: false }
|
||||
|
||||
this.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
ctx.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
|
||||
expect(res.locals.hasAdminCapability('capability1')).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('when req.adminCapabilitiesAvailable is undefined', function () {
|
||||
it('returns false for any capability', function () {
|
||||
it('returns false for any capability', function (ctx) {
|
||||
const req = new MockRequest()
|
||||
const res = new MockResponse()
|
||||
const next = sinon.stub()
|
||||
|
||||
req.session.user = { isAdmin: false }
|
||||
|
||||
this.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
ctx.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
|
||||
expect(res.locals.hasAdminCapability('capability1')).to.be.false
|
||||
})
|
||||
@@ -319,7 +321,7 @@ describe('AdminAuthorizationHelper', function () {
|
||||
|
||||
describe('user is an admin', function () {
|
||||
describe('when req.adminCapabilitiesAvailable is false', function () {
|
||||
it('returns true for any capability', function () {
|
||||
it('returns true for any capability', function (ctx) {
|
||||
const req = new MockRequest()
|
||||
const res = new MockResponse()
|
||||
const next = sinon.stub()
|
||||
@@ -327,21 +329,21 @@ describe('AdminAuthorizationHelper', function () {
|
||||
req.session.user = { isAdmin: true }
|
||||
req.adminCapabilitiesAvailable = false
|
||||
|
||||
this.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
ctx.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
|
||||
expect(res.locals.hasAdminCapability('capability1')).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('when req.adminCapabilitiesAvailable is undefined', function () {
|
||||
it('returns true for any capability', function () {
|
||||
it('returns true for any capability', function (ctx) {
|
||||
const req = new MockRequest()
|
||||
const res = new MockResponse()
|
||||
const next = sinon.stub()
|
||||
|
||||
req.session.user = { isAdmin: true }
|
||||
|
||||
this.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
ctx.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
|
||||
expect(res.locals.hasAdminCapability('capability1')).to.be.true
|
||||
})
|
||||
@@ -349,7 +351,7 @@ describe('AdminAuthorizationHelper', function () {
|
||||
|
||||
describe('when req.adminCapabilitiesAvailable is true', function () {
|
||||
let req, res, next
|
||||
beforeEach(function () {
|
||||
beforeEach(function (ctx) {
|
||||
req = new MockRequest()
|
||||
res = new MockResponse()
|
||||
next = sinon.stub()
|
||||
@@ -358,7 +360,7 @@ describe('AdminAuthorizationHelper', function () {
|
||||
req.adminCapabilitiesAvailable = true
|
||||
req.adminCapabilities = ['capability1', 'capability2']
|
||||
|
||||
this.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
ctx.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
|
||||
})
|
||||
|
||||
it('returns true for a capability the user has', function () {
|
||||
@@ -373,20 +375,20 @@ describe('AdminAuthorizationHelper', function () {
|
||||
})
|
||||
describe('hasAdminCapability', function () {
|
||||
describe('when user is not an admin', function () {
|
||||
it('returns false', function () {
|
||||
it('returns false', function (ctx) {
|
||||
const req = {
|
||||
session: {
|
||||
user: { isAdmin: false },
|
||||
},
|
||||
}
|
||||
expect(
|
||||
this.AdminAuthorizationHelper.hasAdminCapability('capability')(req)
|
||||
ctx.AdminAuthorizationHelper.hasAdminCapability('capability')(req)
|
||||
).to.be.false
|
||||
})
|
||||
})
|
||||
describe('when user is an admin', function () {
|
||||
describe('when adminCapabilitiesAvailable is falsey', function () {
|
||||
it('returns true', function () {
|
||||
it('returns true', function (ctx) {
|
||||
const req = {
|
||||
session: {
|
||||
user: { isAdmin: true },
|
||||
@@ -394,22 +396,22 @@ describe('AdminAuthorizationHelper', function () {
|
||||
adminCapabilitiesAvailable: false,
|
||||
}
|
||||
expect(
|
||||
this.AdminAuthorizationHelper.hasAdminCapability('capability')(req)
|
||||
ctx.AdminAuthorizationHelper.hasAdminCapability('capability')(req)
|
||||
).to.be.true
|
||||
})
|
||||
it('ignores the "requireAdminRoles" argument', function () {
|
||||
it('ignores the "requireAdminRoles" argument', function (ctx) {
|
||||
const req = {
|
||||
session: { user: { isAdmin: true } },
|
||||
adminCapabilitiesAvailable: false,
|
||||
}
|
||||
expect(
|
||||
this.AdminAuthorizationHelper.hasAdminCapability(
|
||||
ctx.AdminAuthorizationHelper.hasAdminCapability(
|
||||
'capability',
|
||||
true
|
||||
)(req)
|
||||
).to.be.true
|
||||
expect(
|
||||
this.AdminAuthorizationHelper.hasAdminCapability(
|
||||
ctx.AdminAuthorizationHelper.hasAdminCapability(
|
||||
'capability',
|
||||
false
|
||||
)(req)
|
||||
@@ -418,30 +420,26 @@ describe('AdminAuthorizationHelper', function () {
|
||||
})
|
||||
describe('when adminCapabilitiesAvailable is true', function () {
|
||||
describe('when user has the requested capability', function () {
|
||||
it('returns true', function () {
|
||||
it('returns true', function (ctx) {
|
||||
const req = {
|
||||
session: { user: { isAdmin: true } },
|
||||
adminCapabilitiesAvailable: true,
|
||||
adminCapabilities: ['capability'],
|
||||
}
|
||||
expect(
|
||||
this.AdminAuthorizationHelper.hasAdminCapability('capability')(
|
||||
req
|
||||
)
|
||||
ctx.AdminAuthorizationHelper.hasAdminCapability('capability')(req)
|
||||
).to.be.true
|
||||
})
|
||||
})
|
||||
describe('when user does not have the requested capability', function () {
|
||||
it('returns false', function () {
|
||||
it('returns false', function (ctx) {
|
||||
const req = {
|
||||
session: { user: { isAdmin: true } },
|
||||
adminCapabilitiesAvailable: true,
|
||||
adminCapabilities: ['other-capability'],
|
||||
}
|
||||
expect(
|
||||
this.AdminAuthorizationHelper.hasAdminCapability('capability')(
|
||||
req
|
||||
)
|
||||
ctx.AdminAuthorizationHelper.hasAdminCapability('capability')(req)
|
||||
).to.be.false
|
||||
})
|
||||
})
|
||||
@@ -449,26 +447,26 @@ describe('AdminAuthorizationHelper', function () {
|
||||
})
|
||||
|
||||
describe('when admin roles are not enabled', function () {
|
||||
beforeEach(function () {
|
||||
this.settings.adminRolesEnabled = false
|
||||
beforeEach(function (ctx) {
|
||||
ctx.settings.adminRolesEnabled = false
|
||||
})
|
||||
|
||||
it('returns false even for admins', function () {
|
||||
it('returns false even for admins', function (ctx) {
|
||||
const req = { session: { user: { isAdmin: true } } }
|
||||
expect(
|
||||
this.AdminAuthorizationHelper.hasAdminCapability('capability')(req)
|
||||
ctx.AdminAuthorizationHelper.hasAdminCapability('capability')(req)
|
||||
).to.be.false
|
||||
expect(
|
||||
this.AdminAuthorizationHelper.hasAdminCapability(
|
||||
ctx.AdminAuthorizationHelper.hasAdminCapability(
|
||||
'capability',
|
||||
true
|
||||
)(req)
|
||||
).to.be.false
|
||||
})
|
||||
it('returns true when requireAdminRoles=false', function () {
|
||||
it('returns true when requireAdminRoles=false', function (ctx) {
|
||||
const req = { session: { user: { isAdmin: true } } }
|
||||
expect(
|
||||
this.AdminAuthorizationHelper.hasAdminCapability(
|
||||
ctx.AdminAuthorizationHelper.hasAdminCapability(
|
||||
'capability',
|
||||
false
|
||||
)(req)
|
||||
|
||||
@@ -82,9 +82,12 @@ describe('HistoryController', function () {
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/History/HistoryManager.mjs', () => ({
|
||||
default: ctx.HistoryManager,
|
||||
}))
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/History/HistoryManager.mjs',
|
||||
() => ({
|
||||
default: ctx.HistoryManager,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectDetailsHandler.mjs',
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
const {
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import SandboxedModule from 'sandboxed-module'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import {
|
||||
cleanupTestDatabase,
|
||||
db,
|
||||
waitForDb,
|
||||
} = require('../../../../app/src/infrastructure/mongodb')
|
||||
} from '../../../../app/src/infrastructure/mongodb.js'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const MODULE_PATH = '../../../../app/src/Features/History/HistoryManager'
|
||||
|
||||
|
||||
@@ -23,61 +23,64 @@ describe('RestoreManager', function () {
|
||||
default: Errors,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/History/HistoryManager.mjs', () => ({
|
||||
default: (ctx.HistoryManager = {
|
||||
promises: {
|
||||
getContentAtVersion: sinon.stub().resolves({
|
||||
// Raw snapshot data that will be passed to Snapshot.fromRaw
|
||||
files: {
|
||||
'main.tex': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
editorId: 'test-editor',
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/History/HistoryManager.mjs',
|
||||
() => ({
|
||||
default: (ctx.HistoryManager = {
|
||||
promises: {
|
||||
getContentAtVersion: sinon.stub().resolves({
|
||||
// Raw snapshot data that will be passed to Snapshot.fromRaw
|
||||
files: {
|
||||
'main.tex': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
editorId: 'test-editor',
|
||||
},
|
||||
},
|
||||
'foo.tex': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
editorId: 'test-editor',
|
||||
},
|
||||
},
|
||||
'folder/file.tex': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
editorId: 'test-editor',
|
||||
},
|
||||
},
|
||||
'foo.png': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
provider: 'bar',
|
||||
},
|
||||
},
|
||||
'linkedFile.bib': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
provider: 'mendeley',
|
||||
},
|
||||
},
|
||||
'withMainTrue.tex': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
main: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
'foo.tex': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
editorId: 'test-editor',
|
||||
},
|
||||
},
|
||||
'folder/file.tex': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
editorId: 'test-editor',
|
||||
},
|
||||
},
|
||||
'foo.png': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
provider: 'bar',
|
||||
},
|
||||
},
|
||||
'linkedFile.bib': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
provider: 'mendeley',
|
||||
},
|
||||
},
|
||||
'withMainTrue.tex': {
|
||||
hash: 'abcdef1234567890abcdef1234567890abcdef12',
|
||||
stringLength: 100,
|
||||
metadata: {
|
||||
main: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
requestBlob: sinon.stub().resolves({ stream: ctx.blobStream }),
|
||||
},
|
||||
}),
|
||||
}))
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
requestBlob: sinon.stub().resolves({ stream: ctx.blobStream }),
|
||||
},
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/Metrics.js', () => ({
|
||||
default: {
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,37 +1,34 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const modulePath = require('path').join(
|
||||
__dirname,
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
const modulePath =
|
||||
'../../../../app/src/Features/Subscription/LimitationsManager'
|
||||
)
|
||||
|
||||
describe('LimitationsManager', function () {
|
||||
beforeEach(function () {
|
||||
this.user = {
|
||||
_id: (this.userId = 'user-id'),
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.user = {
|
||||
_id: (ctx.userId = 'user-id'),
|
||||
features: { collaborators: 1 },
|
||||
}
|
||||
this.project = {
|
||||
_id: (this.projectId = 'project-id'),
|
||||
owner_ref: this.userId,
|
||||
ctx.project = {
|
||||
_id: (ctx.projectId = 'project-id'),
|
||||
owner_ref: ctx.userId,
|
||||
}
|
||||
this.ProjectGetter = {
|
||||
ctx.ProjectGetter = {
|
||||
promises: {
|
||||
getProject: sinon.stub().callsFake(async (projectId, fields) => {
|
||||
if (projectId === this.projectId) {
|
||||
return this.project
|
||||
if (projectId === ctx.projectId) {
|
||||
return ctx.project
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
this.UserGetter = {
|
||||
ctx.UserGetter = {
|
||||
promises: {
|
||||
getUser: sinon.stub().callsFake(async (userId, filter) => {
|
||||
if (userId === this.userId) {
|
||||
return this.user
|
||||
if (userId === ctx.userId) {
|
||||
return ctx.user
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
@@ -39,7 +36,7 @@ describe('LimitationsManager', function () {
|
||||
},
|
||||
}
|
||||
|
||||
this.SubscriptionLocator = {
|
||||
ctx.SubscriptionLocator = {
|
||||
promises: {
|
||||
getUsersSubscription: sinon.stub().resolves(),
|
||||
getSubscription: sinon.stub().resolves(),
|
||||
@@ -47,161 +44,194 @@ describe('LimitationsManager', function () {
|
||||
},
|
||||
}
|
||||
|
||||
this.CollaboratorsGetter = {
|
||||
ctx.CollaboratorsGetter = {
|
||||
promises: {
|
||||
getInvitedEditCollaboratorCount: sinon.stub().resolves(0),
|
||||
getMemberIdPrivilegeLevel: sinon.stub(),
|
||||
},
|
||||
}
|
||||
|
||||
this.CollaboratorsInviteGetter = {
|
||||
ctx.CollaboratorsInviteGetter = {
|
||||
promises: {
|
||||
getEditInviteCount: sinon.stub().resolves(0),
|
||||
},
|
||||
}
|
||||
|
||||
this.LimitationsManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../Project/ProjectGetter': this.ProjectGetter,
|
||||
'../User/UserGetter': this.UserGetter,
|
||||
'./SubscriptionLocator': this.SubscriptionLocator,
|
||||
'@overleaf/settings': (this.Settings = {}),
|
||||
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
|
||||
'../Collaborators/CollaboratorsInviteGetter':
|
||||
this.CollaboratorsInviteGetter,
|
||||
'./V1SubscriptionManager': this.V1SubscriptionManager,
|
||||
},
|
||||
})
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
|
||||
default: ctx.ProjectGetter,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: ctx.UserGetter,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/SubscriptionLocator',
|
||||
() => ({
|
||||
default: ctx.SubscriptionLocator,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: (ctx.Settings = {}),
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Collaborators/CollaboratorsGetter',
|
||||
() => ({
|
||||
default: ctx.CollaboratorsGetter,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Collaborators/CollaboratorsInviteGetter',
|
||||
() => ({
|
||||
default: ctx.CollaboratorsInviteGetter,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/V1SubscriptionManager',
|
||||
() => ({
|
||||
default: ctx.V1SubscriptionManager,
|
||||
})
|
||||
)
|
||||
|
||||
ctx.LimitationsManager = (await import(modulePath)).default
|
||||
})
|
||||
|
||||
describe('allowedNumberOfCollaboratorsInProject', function () {
|
||||
describe('when the project is owned by a user without a subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.Settings.defaultFeatures = { collaborators: 23 }
|
||||
this.project.owner_ref = this.userId
|
||||
delete this.user.features
|
||||
beforeEach(function (ctx) {
|
||||
ctx.Settings.defaultFeatures = { collaborators: 23 }
|
||||
ctx.project.owner_ref = ctx.userId
|
||||
delete ctx.user.features
|
||||
})
|
||||
|
||||
it('should return the default number', async function () {
|
||||
it('should return the default number', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.allowedNumberOfCollaboratorsInProject(
|
||||
this.projectId
|
||||
await ctx.LimitationsManager.promises.allowedNumberOfCollaboratorsInProject(
|
||||
ctx.projectId
|
||||
)
|
||||
expect(result).to.equal(this.Settings.defaultFeatures.collaborators)
|
||||
expect(result).to.equal(ctx.Settings.defaultFeatures.collaborators)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project is owned by a user with a subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.project.owner_ref = this.userId
|
||||
this.user.features = { collaborators: 21 }
|
||||
beforeEach(function (ctx) {
|
||||
ctx.project.owner_ref = ctx.userId
|
||||
ctx.user.features = { collaborators: 21 }
|
||||
})
|
||||
|
||||
it('should return the number of collaborators the user is allowed', async function () {
|
||||
it('should return the number of collaborators the user is allowed', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.allowedNumberOfCollaboratorsInProject(
|
||||
this.projectId
|
||||
await ctx.LimitationsManager.promises.allowedNumberOfCollaboratorsInProject(
|
||||
ctx.projectId
|
||||
)
|
||||
expect(result).to.equal(this.user.features.collaborators)
|
||||
expect(result).to.equal(ctx.user.features.collaborators)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('allowedNumberOfCollaboratorsForUser', function () {
|
||||
describe('when the user has no features', function () {
|
||||
beforeEach(function () {
|
||||
this.Settings.defaultFeatures = { collaborators: 23 }
|
||||
delete this.user.features
|
||||
beforeEach(function (ctx) {
|
||||
ctx.Settings.defaultFeatures = { collaborators: 23 }
|
||||
delete ctx.user.features
|
||||
})
|
||||
|
||||
it('should return the default number', async function () {
|
||||
it('should return the default number', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser(
|
||||
this.userId
|
||||
await ctx.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser(
|
||||
ctx.userId
|
||||
)
|
||||
expect(result).to.equal(this.Settings.defaultFeatures.collaborators)
|
||||
expect(result).to.equal(ctx.Settings.defaultFeatures.collaborators)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the user has features', function () {
|
||||
beforeEach(async function () {
|
||||
this.user.features = { collaborators: 21 }
|
||||
this.result =
|
||||
await this.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser(
|
||||
this.userId
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.user.features = { collaborators: 21 }
|
||||
ctx.result =
|
||||
await ctx.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser(
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('should return the number of collaborators the user is allowed', function () {
|
||||
expect(this.result).to.equal(this.user.features.collaborators)
|
||||
it('should return the number of collaborators the user is allowed', function (ctx) {
|
||||
expect(ctx.result).to.equal(ctx.user.features.collaborators)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('canAcceptEditCollaboratorInvite', function () {
|
||||
describe('when the project has fewer collaborators than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 1
|
||||
this.user.features.collaborators = 2
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 1
|
||||
ctx.user.features.collaborators = 2
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.current_number)
|
||||
})
|
||||
|
||||
it('should return true', async function () {
|
||||
it('should return true', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
this.projectId
|
||||
await ctx.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
ctx.projectId
|
||||
)
|
||||
expect(result).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('when accepting the invite would exceed the collaborator limit', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 2
|
||||
this.user.features.collaborators = 2
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 2
|
||||
ctx.user.features.collaborators = 2
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.current_number)
|
||||
})
|
||||
|
||||
it('should return false', async function () {
|
||||
it('should return false', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
this.projectId
|
||||
await ctx.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
ctx.projectId
|
||||
)
|
||||
expect(result).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has more collaborators than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 3
|
||||
this.user.features.collaborators = 2
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 3
|
||||
ctx.user.features.collaborators = 2
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.current_number)
|
||||
})
|
||||
|
||||
it('should return false', async function () {
|
||||
it('should return false', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
this.projectId
|
||||
await ctx.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
ctx.projectId
|
||||
)
|
||||
expect(result).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has infinite collaborators', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 100
|
||||
this.user.features.collaborators = -1
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 100
|
||||
ctx.user.features.collaborators = -1
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.current_number)
|
||||
})
|
||||
|
||||
it('should return true', async function () {
|
||||
it('should return true', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
this.projectId
|
||||
await ctx.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
ctx.projectId
|
||||
)
|
||||
expect(result).to.be.true
|
||||
})
|
||||
@@ -210,21 +240,22 @@ describe('LimitationsManager', function () {
|
||||
|
||||
describe('canAddXEditCollaborators', function () {
|
||||
describe('when the project has fewer collaborators than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 1
|
||||
this.user.features.collaborators = 2
|
||||
this.invite_count = 0
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 1
|
||||
ctx.user.features.collaborators = 2
|
||||
ctx.invite_count = 0
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
.resolves(ctx.current_number)
|
||||
ctx.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.invite_count)
|
||||
})
|
||||
|
||||
it('should return true', async function () {
|
||||
it('should return true', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
this.projectId,
|
||||
await ctx.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
ctx.projectId,
|
||||
1
|
||||
)
|
||||
expect(result).to.be.true
|
||||
@@ -232,21 +263,22 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('when the project has fewer collaborators and invites than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 1
|
||||
this.user.features.collaborators = 4
|
||||
this.invite_count = 1
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 1
|
||||
ctx.user.features.collaborators = 4
|
||||
ctx.invite_count = 1
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
.resolves(ctx.current_number)
|
||||
ctx.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.invite_count)
|
||||
})
|
||||
|
||||
it('should return true', async function () {
|
||||
it('should return true', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
this.projectId,
|
||||
await ctx.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
ctx.projectId,
|
||||
1
|
||||
)
|
||||
expect(result).to.be.true
|
||||
@@ -254,21 +286,22 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('when the project has fewer collaborators than allowed but I want to add more than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 1
|
||||
this.user.features.collaborators = 2
|
||||
this.invite_count = 0
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 1
|
||||
ctx.user.features.collaborators = 2
|
||||
ctx.invite_count = 0
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
.resolves(ctx.current_number)
|
||||
ctx.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.invite_count)
|
||||
})
|
||||
|
||||
it('should return false', async function () {
|
||||
it('should return false', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
this.projectId,
|
||||
await ctx.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
ctx.projectId,
|
||||
2
|
||||
)
|
||||
expect(result).to.be.false
|
||||
@@ -276,21 +309,22 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('when the project has more collaborators than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 3
|
||||
this.user.features.collaborators = 2
|
||||
this.invite_count = 0
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 3
|
||||
ctx.user.features.collaborators = 2
|
||||
ctx.invite_count = 0
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
.resolves(ctx.current_number)
|
||||
ctx.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.invite_count)
|
||||
})
|
||||
|
||||
it('should return false', async function () {
|
||||
it('should return false', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
this.projectId,
|
||||
await ctx.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
ctx.projectId,
|
||||
1
|
||||
)
|
||||
expect(result).to.be.false
|
||||
@@ -298,21 +332,22 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('when the project has infinite collaborators', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 100
|
||||
this.user.features.collaborators = -1
|
||||
this.invite_count = 0
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 100
|
||||
ctx.user.features.collaborators = -1
|
||||
ctx.invite_count = 0
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
.resolves(ctx.current_number)
|
||||
ctx.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.invite_count)
|
||||
})
|
||||
|
||||
it('should return true', async function () {
|
||||
it('should return true', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
this.projectId,
|
||||
await ctx.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
ctx.projectId,
|
||||
1
|
||||
)
|
||||
expect(result).to.be.true
|
||||
@@ -320,21 +355,22 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('when the project has more invites than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 0
|
||||
this.user.features.collaborators = 2
|
||||
this.invite_count = 2
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 0
|
||||
ctx.user.features.collaborators = 2
|
||||
ctx.invite_count = 2
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
.resolves(ctx.current_number)
|
||||
ctx.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.invite_count)
|
||||
})
|
||||
|
||||
it('should return false', async function () {
|
||||
it('should return false', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
this.projectId,
|
||||
await ctx.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
ctx.projectId,
|
||||
1
|
||||
)
|
||||
expect(result).to.be.false
|
||||
@@ -342,21 +378,22 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('when the project has more invites and collaborators than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 1
|
||||
this.user.features.collaborators = 2
|
||||
this.invite_count = 1
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.current_number = 1
|
||||
ctx.user.features.collaborators = 2
|
||||
ctx.invite_count = 1
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
.resolves(ctx.current_number)
|
||||
ctx.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(ctx.invite_count)
|
||||
})
|
||||
|
||||
it('should return false', async function () {
|
||||
it('should return false', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
this.projectId,
|
||||
await ctx.LimitationsManager.promises.canAddXEditCollaborators(
|
||||
ctx.projectId,
|
||||
1
|
||||
)
|
||||
expect(result).to.be.false
|
||||
@@ -365,19 +402,19 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('canChangeCollaboratorPrivilegeLevel', function () {
|
||||
beforeEach(function () {
|
||||
this.collaboratorId = 'collaborator-id'
|
||||
this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.resolves(
|
||||
beforeEach(function (ctx) {
|
||||
ctx.collaboratorId = 'collaborator-id'
|
||||
ctx.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.resolves(
|
||||
'readOnly'
|
||||
)
|
||||
})
|
||||
|
||||
describe("when the limit hasn't been reached", function () {
|
||||
it('accepts changing a viewer to an editor', async function () {
|
||||
it('accepts changing a viewer to an editor', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
|
||||
this.projectId,
|
||||
this.collaboratorId,
|
||||
await ctx.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
|
||||
ctx.projectId,
|
||||
ctx.collaboratorId,
|
||||
'readAndWrite'
|
||||
)
|
||||
expect(result).to.be.true
|
||||
@@ -385,30 +422,30 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('when the limit has been reached', function () {
|
||||
beforeEach(function () {
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount.resolves(
|
||||
beforeEach(function (ctx) {
|
||||
ctx.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount.resolves(
|
||||
1
|
||||
)
|
||||
})
|
||||
|
||||
it('accepts changing a reviewer to an editor', async function () {
|
||||
this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.resolves(
|
||||
it('accepts changing a reviewer to an editor', async function (ctx) {
|
||||
ctx.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.resolves(
|
||||
'review'
|
||||
)
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
|
||||
this.projectId,
|
||||
this.collaboratorId,
|
||||
await ctx.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
|
||||
ctx.projectId,
|
||||
ctx.collaboratorId,
|
||||
'readAndWrite'
|
||||
)
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('rejects changing a viewer to a reviewer', async function () {
|
||||
it('rejects changing a viewer to a reviewer', async function (ctx) {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
|
||||
this.projectId,
|
||||
this.collaboratorId,
|
||||
await ctx.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
|
||||
ctx.projectId,
|
||||
ctx.collaboratorId,
|
||||
'review'
|
||||
)
|
||||
expect(result).to.be.false
|
||||
@@ -417,25 +454,25 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('userHasSubscription', function () {
|
||||
beforeEach(function () {
|
||||
this.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
})
|
||||
|
||||
it('should return true if the recurly token is set', async function () {
|
||||
this.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
it('should return true if the recurly token is set', async function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
.stub()
|
||||
.resolves({
|
||||
recurlySubscription_id: '1234',
|
||||
})
|
||||
const { hasSubscription } =
|
||||
await this.LimitationsManager.promises.userHasSubscription(this.user)
|
||||
await ctx.LimitationsManager.promises.userHasSubscription(ctx.user)
|
||||
expect(hasSubscription).to.be.true
|
||||
})
|
||||
|
||||
it('should return true if the paymentProvider field is set', async function () {
|
||||
this.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
it('should return true if the paymentProvider field is set', async function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
.stub()
|
||||
.resolves({
|
||||
paymentProvider: {
|
||||
@@ -443,80 +480,80 @@ describe('LimitationsManager', function () {
|
||||
},
|
||||
})
|
||||
const { hasSubscription } =
|
||||
await this.LimitationsManager.promises.userHasSubscription(this.user)
|
||||
await ctx.LimitationsManager.promises.userHasSubscription(ctx.user)
|
||||
expect(hasSubscription).to.be.true
|
||||
})
|
||||
|
||||
it('should return false if the recurly token is not set', async function () {
|
||||
this.SubscriptionLocator.promises.getUsersSubscription.resolves({})
|
||||
it('should return false if the recurly token is not set', async function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription.resolves({})
|
||||
const { hasSubscription } =
|
||||
await this.LimitationsManager.promises.userHasSubscription(this.user)
|
||||
await ctx.LimitationsManager.promises.userHasSubscription(ctx.user)
|
||||
expect(hasSubscription).to.be.false
|
||||
})
|
||||
|
||||
it('should return false if the subscription is undefined', async function () {
|
||||
this.SubscriptionLocator.promises.getUsersSubscription.resolves()
|
||||
it('should return false if the subscription is undefined', async function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription.resolves()
|
||||
const { hasSubscription } =
|
||||
await this.LimitationsManager.promises.userHasSubscription(this.user)
|
||||
await ctx.LimitationsManager.promises.userHasSubscription(ctx.user)
|
||||
expect(hasSubscription).to.be.false
|
||||
})
|
||||
|
||||
it('should return the subscription', async function () {
|
||||
it('should return the subscription', async function (ctx) {
|
||||
const stubbedSubscription = { freeTrial: {}, token: '' }
|
||||
this.SubscriptionLocator.promises.getUsersSubscription.resolves(
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription.resolves(
|
||||
stubbedSubscription
|
||||
)
|
||||
const { subscription } =
|
||||
await this.LimitationsManager.promises.userHasSubscription(this.user)
|
||||
await ctx.LimitationsManager.promises.userHasSubscription(ctx.user)
|
||||
expect(subscription).to.deep.equal(stubbedSubscription)
|
||||
})
|
||||
|
||||
describe('when user has a custom account', function () {
|
||||
beforeEach(function () {
|
||||
this.fakeSubscription = { customAccount: true }
|
||||
this.SubscriptionLocator.promises.getUsersSubscription.resolves(
|
||||
this.fakeSubscription
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fakeSubscription = { customAccount: true }
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription.resolves(
|
||||
ctx.fakeSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('should return true', async function () {
|
||||
it('should return true', async function (ctx) {
|
||||
const { hasSubscription } =
|
||||
await this.LimitationsManager.promises.userHasSubscription(this.user)
|
||||
await ctx.LimitationsManager.promises.userHasSubscription(ctx.user)
|
||||
expect(hasSubscription).to.be.true
|
||||
})
|
||||
|
||||
it('should return the subscription', async function () {
|
||||
it('should return the subscription', async function (ctx) {
|
||||
const { subscription } =
|
||||
await this.LimitationsManager.promises.userHasSubscription(this.user)
|
||||
expect(subscription).to.deep.equal(this.fakeSubscription)
|
||||
await ctx.LimitationsManager.promises.userHasSubscription(ctx.user)
|
||||
expect(subscription).to.deep.equal(ctx.fakeSubscription)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('userIsMemberOfGroupSubscription', function () {
|
||||
beforeEach(function () {
|
||||
this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getMemberSubscriptions = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
})
|
||||
|
||||
it('should return false if there are no groups subcriptions', async function () {
|
||||
this.SubscriptionLocator.promises.getMemberSubscriptions.resolves([])
|
||||
it('should return false if there are no groups subcriptions', async function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getMemberSubscriptions.resolves([])
|
||||
const { isMember } =
|
||||
await this.LimitationsManager.promises.userIsMemberOfGroupSubscription(
|
||||
this.user
|
||||
await ctx.LimitationsManager.promises.userIsMemberOfGroupSubscription(
|
||||
ctx.user
|
||||
)
|
||||
expect(isMember).to.be.false
|
||||
})
|
||||
|
||||
it('should return true if there are no groups subcriptions', async function () {
|
||||
it('should return true if there are no groups subcriptions', async function (ctx) {
|
||||
const subscriptions = ['mock-subscription']
|
||||
this.SubscriptionLocator.promises.getMemberSubscriptions.resolves(
|
||||
ctx.SubscriptionLocator.promises.getMemberSubscriptions.resolves(
|
||||
subscriptions
|
||||
)
|
||||
const { isMember, subscriptions: retSubscriptions } =
|
||||
await this.LimitationsManager.promises.userIsMemberOfGroupSubscription(
|
||||
this.user
|
||||
await ctx.LimitationsManager.promises.userIsMemberOfGroupSubscription(
|
||||
ctx.user
|
||||
)
|
||||
expect(isMember).to.be.true
|
||||
expect(retSubscriptions).to.deep.equal(subscriptions)
|
||||
@@ -524,52 +561,52 @@ describe('LimitationsManager', function () {
|
||||
})
|
||||
|
||||
describe('hasPaidSubscription', function () {
|
||||
beforeEach(function () {
|
||||
this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getMemberSubscriptions = sinon
|
||||
.stub()
|
||||
.resolves([])
|
||||
this.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
.stub()
|
||||
.resolves(null)
|
||||
})
|
||||
|
||||
it('should return true if userIsMemberOfGroupSubscription', async function () {
|
||||
this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
|
||||
it('should return true if userIsMemberOfGroupSubscription', async function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getMemberSubscriptions = sinon
|
||||
.stub()
|
||||
.resolves([{ _id: '123' }])
|
||||
const { hasPaidSubscription } =
|
||||
await this.LimitationsManager.promises.hasPaidSubscription(this.user)
|
||||
await ctx.LimitationsManager.promises.hasPaidSubscription(ctx.user)
|
||||
expect(hasPaidSubscription).to.be.true
|
||||
})
|
||||
|
||||
it('should return true if userHasSubscription', async function () {
|
||||
this.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
it('should return true if userHasSubscription', async function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription = sinon
|
||||
.stub()
|
||||
.resolves({ recurlySubscription_id: '123' })
|
||||
const { hasPaidSubscription } =
|
||||
await this.LimitationsManager.promises.hasPaidSubscription(this.user)
|
||||
await ctx.LimitationsManager.promises.hasPaidSubscription(ctx.user)
|
||||
expect(hasPaidSubscription).to.be.true
|
||||
})
|
||||
|
||||
it('should return false if none are true', async function () {
|
||||
it('should return false if none are true', async function (ctx) {
|
||||
const { hasPaidSubscription } =
|
||||
await this.LimitationsManager.promises.hasPaidSubscription(this.user)
|
||||
await ctx.LimitationsManager.promises.hasPaidSubscription(ctx.user)
|
||||
expect(hasPaidSubscription).to.be.false
|
||||
})
|
||||
|
||||
it('should have userHasSubscriptionOrIsGroupMember alias', async function () {
|
||||
it('should have userHasSubscriptionOrIsGroupMember alias', async function (ctx) {
|
||||
const { hasPaidSubscription } =
|
||||
await this.LimitationsManager.promises.userHasSubscriptionOrIsGroupMember(
|
||||
this.user
|
||||
await ctx.LimitationsManager.promises.userHasSubscriptionOrIsGroupMember(
|
||||
ctx.user
|
||||
)
|
||||
expect(hasPaidSubscription).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasGroupMembersLimitReached', function () {
|
||||
beforeEach(function () {
|
||||
this.subscriptionId = '12312'
|
||||
this.subscription = {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.subscriptionId = '12312'
|
||||
ctx.subscription = {
|
||||
membersLimit: 3,
|
||||
member_ids: ['', ''],
|
||||
teamInvites: [
|
||||
@@ -578,37 +615,37 @@ describe('LimitationsManager', function () {
|
||||
}
|
||||
})
|
||||
|
||||
it('should return true if the limit is hit (including members and invites)', async function () {
|
||||
this.SubscriptionLocator.promises.getSubscription.resolves(
|
||||
this.subscription
|
||||
it('should return true if the limit is hit (including members and invites)', async function (ctx) {
|
||||
ctx.SubscriptionLocator.promises.getSubscription.resolves(
|
||||
ctx.subscription
|
||||
)
|
||||
const { limitReached } =
|
||||
await this.LimitationsManager.promises.hasGroupMembersLimitReached(
|
||||
this.subscriptionId
|
||||
await ctx.LimitationsManager.promises.hasGroupMembersLimitReached(
|
||||
ctx.subscriptionId
|
||||
)
|
||||
expect(limitReached).to.be.true
|
||||
})
|
||||
|
||||
it('should return false if the limit is not hit (including members and invites)', async function () {
|
||||
this.subscription.membersLimit = 4
|
||||
this.SubscriptionLocator.promises.getSubscription.resolves(
|
||||
this.subscription
|
||||
it('should return false if the limit is not hit (including members and invites)', async function (ctx) {
|
||||
ctx.subscription.membersLimit = 4
|
||||
ctx.SubscriptionLocator.promises.getSubscription.resolves(
|
||||
ctx.subscription
|
||||
)
|
||||
const { limitReached } =
|
||||
await this.LimitationsManager.promises.hasGroupMembersLimitReached(
|
||||
this.subscriptionId
|
||||
await ctx.LimitationsManager.promises.hasGroupMembersLimitReached(
|
||||
ctx.subscriptionId
|
||||
)
|
||||
expect(limitReached).to.be.false
|
||||
})
|
||||
|
||||
it('should return true if the limit has been exceded (including members and invites)', async function () {
|
||||
this.subscription.membersLimit = 2
|
||||
this.SubscriptionLocator.promises.getSubscription.resolves(
|
||||
this.subscription
|
||||
it('should return true if the limit has been exceded (including members and invites)', async function (ctx) {
|
||||
ctx.subscription.membersLimit = 2
|
||||
ctx.SubscriptionLocator.promises.getSubscription.resolves(
|
||||
ctx.subscription
|
||||
)
|
||||
const { limitReached } =
|
||||
await this.LimitationsManager.promises.hasGroupMembersLimitReached(
|
||||
this.subscriptionId
|
||||
await ctx.LimitationsManager.promises.hasGroupMembersLimitReached(
|
||||
ctx.subscriptionId
|
||||
)
|
||||
expect(limitReached).to.be.true
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,20 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import Errors from '../../../../app/src/Features/Errors/Errors.js'
|
||||
const modulePath =
|
||||
'../../../../app/src/Features/Subscription/TeamInvitesHandler'
|
||||
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
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 { ObjectId } = mongodb
|
||||
|
||||
describe('TeamInvitesHandler', function () {
|
||||
beforeEach(function () {
|
||||
this.manager = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.manager = {
|
||||
_id: '666666',
|
||||
first_name: 'Daenerys',
|
||||
last_name: 'Targaryen',
|
||||
@@ -17,34 +22,34 @@ describe('TeamInvitesHandler', function () {
|
||||
emails: [{ email: 'daenerys@example.com' }],
|
||||
}
|
||||
|
||||
this.token = 'aaaaaaaaaaaaaaaaaaaaaa'
|
||||
ctx.token = 'aaaaaaaaaaaaaaaaaaaaaa'
|
||||
|
||||
this.teamInvite = {
|
||||
ctx.teamInvite = {
|
||||
email: 'jorah@example.com',
|
||||
token: this.token,
|
||||
token: ctx.token,
|
||||
}
|
||||
// ensure teamInvite can be converted from Document to Object
|
||||
this.teamInvite.toObject = () => this.teamInvite
|
||||
ctx.teamInvite.toObject = () => ctx.teamInvite
|
||||
|
||||
this.subscription = {
|
||||
ctx.subscription = {
|
||||
id: '55153a8014829a865bbf700d',
|
||||
_id: new ObjectId('55153a8014829a865bbf700d'),
|
||||
recurlySubscription_id: '1a2b3c4d5e6f7g',
|
||||
admin_id: this.manager._id,
|
||||
admin_id: ctx.manager._id,
|
||||
groupPlan: true,
|
||||
member_ids: [],
|
||||
teamInvites: [this.teamInvite],
|
||||
teamInvites: [ctx.teamInvite],
|
||||
save: sinon.stub().resolves(),
|
||||
}
|
||||
|
||||
this.SubscriptionLocator = {
|
||||
ctx.SubscriptionLocator = {
|
||||
promises: {
|
||||
getUsersSubscription: sinon.stub(),
|
||||
getSubscription: sinon.stub().resolves(this.subscription),
|
||||
getSubscription: sinon.stub().resolves(ctx.subscription),
|
||||
},
|
||||
}
|
||||
|
||||
this.UserGetter = {
|
||||
ctx.UserGetter = {
|
||||
promises: {
|
||||
getUser: sinon.stub().resolves(),
|
||||
getUserByAnyEmail: sinon.stub().resolves(),
|
||||
@@ -52,55 +57,55 @@ describe('TeamInvitesHandler', function () {
|
||||
},
|
||||
}
|
||||
|
||||
this.SubscriptionUpdater = {
|
||||
ctx.SubscriptionUpdater = {
|
||||
promises: {
|
||||
addUserToGroup: sinon.stub().resolves(),
|
||||
deleteSubscription: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.LimitationsManager = {
|
||||
ctx.LimitationsManager = {
|
||||
teamHasReachedMemberLimit: sinon.stub().returns(false),
|
||||
}
|
||||
|
||||
this.Subscription = {
|
||||
ctx.Subscription = {
|
||||
findOne: sinon.stub().resolves(),
|
||||
updateOne: sinon.stub().resolves(),
|
||||
}
|
||||
|
||||
this.SSOConfig = {
|
||||
ctx.SSOConfig = {
|
||||
findById: sinon.stub().resolves(),
|
||||
}
|
||||
|
||||
this.EmailHandler = {
|
||||
ctx.EmailHandler = {
|
||||
promises: {
|
||||
sendEmail: sinon.stub().resolves(null),
|
||||
},
|
||||
}
|
||||
|
||||
this.newToken = 'bbbbbbbbb'
|
||||
ctx.newToken = 'bbbbbbbbb'
|
||||
|
||||
this.crypto = {
|
||||
ctx.crypto = {
|
||||
randomBytes: () => {
|
||||
return { toString: sinon.stub().returns(this.newToken) }
|
||||
return { toString: sinon.stub().returns(ctx.newToken) }
|
||||
},
|
||||
}
|
||||
|
||||
this.UserGetter.promises.getUser
|
||||
.withArgs(this.manager._id)
|
||||
.resolves(this.manager)
|
||||
this.UserGetter.promises.getUserByAnyEmail
|
||||
.withArgs(this.manager.email)
|
||||
.resolves(this.manager)
|
||||
this.UserGetter.promises.getUserByMainEmail
|
||||
.withArgs(this.manager.email)
|
||||
.resolves(this.manager)
|
||||
ctx.UserGetter.promises.getUser
|
||||
.withArgs(ctx.manager._id)
|
||||
.resolves(ctx.manager)
|
||||
ctx.UserGetter.promises.getUserByAnyEmail
|
||||
.withArgs(ctx.manager.email)
|
||||
.resolves(ctx.manager)
|
||||
ctx.UserGetter.promises.getUserByMainEmail
|
||||
.withArgs(ctx.manager.email)
|
||||
.resolves(ctx.manager)
|
||||
|
||||
this.SubscriptionLocator.promises.getUsersSubscription.resolves(
|
||||
this.subscription
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription.resolves(
|
||||
ctx.subscription
|
||||
)
|
||||
|
||||
this.NotificationsBuilder = {
|
||||
ctx.NotificationsBuilder = {
|
||||
promises: {
|
||||
groupInvitation: sinon.stub().returns({
|
||||
create: sinon.stub().resolves(),
|
||||
@@ -109,51 +114,105 @@ describe('TeamInvitesHandler', function () {
|
||||
},
|
||||
}
|
||||
|
||||
this.Subscription.findOne.resolves(this.subscription)
|
||||
ctx.Subscription.findOne.resolves(ctx.subscription)
|
||||
|
||||
this.RecurlyClient = {
|
||||
ctx.RecurlyClient = {
|
||||
promises: {
|
||||
terminateSubscriptionByUuid: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.TeamInvitesHandler = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'mongodb-legacy': { ObjectId },
|
||||
crypto: this.crypto,
|
||||
'@overleaf/settings': { siteUrl: 'http://example.com' },
|
||||
'../../models/TeamInvite': { TeamInvite: (this.TeamInvite = {}) },
|
||||
'../../models/Subscription': { Subscription: this.Subscription },
|
||||
'../../models/SSOConfig': { SSOConfig: this.SSOConfig },
|
||||
'../User/UserGetter': this.UserGetter,
|
||||
'./SubscriptionLocator': this.SubscriptionLocator,
|
||||
'./SubscriptionUpdater': this.SubscriptionUpdater,
|
||||
'./LimitationsManager': this.LimitationsManager,
|
||||
'../Email/EmailHandler': this.EmailHandler,
|
||||
'../Notifications/NotificationsBuilder': this.NotificationsBuilder,
|
||||
'../../infrastructure/Modules': (this.Modules = {
|
||||
promises: { hooks: { fire: sinon.stub().resolves() } },
|
||||
}),
|
||||
'./RecurlyClient': this.RecurlyClient,
|
||||
},
|
||||
})
|
||||
vi.doMock('mongodb-legacy', () => ({
|
||||
default: { ObjectId },
|
||||
}))
|
||||
|
||||
vi.doMock('crypto', () => ({
|
||||
default: ctx.crypto,
|
||||
}))
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: { siteUrl: 'http://example.com' },
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/models/TeamInvite', () => ({
|
||||
TeamInvite: (ctx.TeamInvite = {}),
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/models/Subscription', () => ({
|
||||
Subscription: ctx.Subscription,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/models/SSOConfig', () => ({
|
||||
SSOConfig: ctx.SSOConfig,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: ctx.UserGetter,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/SubscriptionLocator',
|
||||
() => ({
|
||||
default: ctx.SubscriptionLocator,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/SubscriptionUpdater',
|
||||
() => ({
|
||||
default: ctx.SubscriptionUpdater,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/LimitationsManager',
|
||||
() => ({
|
||||
default: ctx.LimitationsManager,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Email/EmailHandler', () => ({
|
||||
default: ctx.EmailHandler,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Notifications/NotificationsBuilder',
|
||||
() => ({
|
||||
default: ctx.NotificationsBuilder,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/Modules', () => ({
|
||||
default: (ctx.Modules = {
|
||||
promises: { hooks: { fire: sinon.stub().resolves() } },
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/RecurlyClient',
|
||||
() => ({
|
||||
default: ctx.RecurlyClient,
|
||||
})
|
||||
)
|
||||
|
||||
ctx.TeamInvitesHandler = (await import(modulePath)).default
|
||||
})
|
||||
|
||||
describe('getInvite', function () {
|
||||
it("returns the invite if there's one", async function () {
|
||||
it("returns the invite if there's one", async function (ctx) {
|
||||
const { invite, subscription } =
|
||||
await this.TeamInvitesHandler.promises.getInvite(this.token)
|
||||
await ctx.TeamInvitesHandler.promises.getInvite(ctx.token)
|
||||
|
||||
expect(invite).to.deep.eq(this.teamInvite)
|
||||
expect(subscription).to.deep.eq(this.subscription)
|
||||
expect(invite).to.deep.eq(ctx.teamInvite)
|
||||
expect(subscription).to.deep.eq(ctx.subscription)
|
||||
})
|
||||
|
||||
it("returns teamNotFound if there's none", async function () {
|
||||
this.Subscription.findOne = sinon.stub().resolves(null)
|
||||
it("returns teamNotFound if there's none", async function (ctx) {
|
||||
ctx.Subscription.findOne = sinon.stub().resolves(null)
|
||||
|
||||
let error
|
||||
try {
|
||||
await this.TeamInvitesHandler.promises.getInvite(this.token)
|
||||
await ctx.TeamInvitesHandler.promises.getInvite(ctx.token)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
@@ -163,66 +222,66 @@ describe('TeamInvitesHandler', function () {
|
||||
})
|
||||
|
||||
describe('createInvite', function () {
|
||||
it('adds the team invite to the subscription', async function () {
|
||||
const invite = await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
it('adds the team invite to the subscription', async function (ctx) {
|
||||
const invite = await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
'John.Snow@example.com'
|
||||
)
|
||||
expect(invite.token).to.eq(this.newToken)
|
||||
expect(invite.token).to.eq(ctx.newToken)
|
||||
expect(invite.email).to.eq('john.snow@example.com')
|
||||
expect(invite.inviterName).to.eq(
|
||||
'Daenerys Targaryen (daenerys@example.com)'
|
||||
)
|
||||
expect(invite.invite).to.be.true
|
||||
expect(this.subscription.teamInvites).to.deep.include(invite)
|
||||
expect(ctx.subscription.teamInvites).to.deep.include(invite)
|
||||
})
|
||||
|
||||
it('sends an email', async function () {
|
||||
await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
it('sends an email', async function (ctx) {
|
||||
await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
'John.Snow@example.com'
|
||||
)
|
||||
|
||||
this.EmailHandler.promises.sendEmail
|
||||
ctx.EmailHandler.promises.sendEmail
|
||||
.calledWith(
|
||||
'verifyEmailToJoinTeam',
|
||||
sinon.match({
|
||||
to: 'john.snow@example.com',
|
||||
inviter: this.manager,
|
||||
acceptInviteUrl: `http://example.com/subscription/invites/${this.newToken}/`,
|
||||
inviter: ctx.manager,
|
||||
acceptInviteUrl: `http://example.com/subscription/invites/${ctx.newToken}/`,
|
||||
})
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('refreshes the existing invite if the email has already been invited', async function () {
|
||||
const originalInvite = Object.assign({}, this.teamInvite)
|
||||
it('refreshes the existing invite if the email has already been invited', async function (ctx) {
|
||||
const originalInvite = Object.assign({}, ctx.teamInvite)
|
||||
|
||||
const invite = await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
const invite = await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
originalInvite.email
|
||||
)
|
||||
expect(invite).to.exist
|
||||
|
||||
expect(this.subscription.teamInvites.length).to.eq(1)
|
||||
expect(this.subscription.teamInvites).to.deep.include(invite)
|
||||
expect(ctx.subscription.teamInvites.length).to.eq(1)
|
||||
expect(ctx.subscription.teamInvites).to.deep.include(invite)
|
||||
|
||||
expect(invite.email).to.eq(originalInvite.email)
|
||||
|
||||
this.subscription.save.calledOnce.should.eq(true)
|
||||
ctx.subscription.save.calledOnce.should.eq(true)
|
||||
})
|
||||
|
||||
it('removes any legacy invite from the subscription', async function () {
|
||||
await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
it('removes any legacy invite from the subscription', async function (ctx) {
|
||||
await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
'John.Snow@example.com'
|
||||
)
|
||||
|
||||
this.Subscription.updateOne
|
||||
ctx.Subscription.updateOne
|
||||
.calledWith(
|
||||
{ _id: new ObjectId('55153a8014829a865bbf700d') },
|
||||
{ $pull: { invited_emails: 'john.snow@example.com' } }
|
||||
@@ -230,77 +289,77 @@ describe('TeamInvitesHandler', function () {
|
||||
.should.eq(true)
|
||||
})
|
||||
|
||||
it('add user to subscription if inviting self', async function () {
|
||||
const invite = await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
this.manager.email
|
||||
it('add user to subscription if inviting self', async function (ctx) {
|
||||
const invite = await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
ctx.manager.email
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.SubscriptionUpdater.promises.addUserToGroup,
|
||||
this.subscription._id,
|
||||
this.manager._id
|
||||
ctx.SubscriptionUpdater.promises.addUserToGroup,
|
||||
ctx.subscription._id,
|
||||
ctx.manager._id
|
||||
)
|
||||
sinon.assert.notCalled(this.subscription.save)
|
||||
sinon.assert.notCalled(ctx.subscription.save)
|
||||
expect(invite.token).to.not.exist
|
||||
expect(invite.email).to.eq(this.manager.email)
|
||||
expect(invite.first_name).to.eq(this.manager.first_name)
|
||||
expect(invite.last_name).to.eq(this.manager.last_name)
|
||||
expect(invite.email).to.eq(ctx.manager.email)
|
||||
expect(invite.first_name).to.eq(ctx.manager.first_name)
|
||||
expect(invite.last_name).to.eq(ctx.manager.last_name)
|
||||
expect(invite.invite).to.be.false
|
||||
})
|
||||
|
||||
it('sends an SSO invite if SSO is enabled and inviting self', async function () {
|
||||
this.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
|
||||
this.SSOConfig.findById
|
||||
.withArgs(this.subscription.ssoConfig)
|
||||
it('sends an SSO invite if SSO is enabled and inviting self', async function (ctx) {
|
||||
ctx.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
|
||||
ctx.SSOConfig.findById
|
||||
.withArgs(ctx.subscription.ssoConfig)
|
||||
.resolves({ enabled: true })
|
||||
|
||||
await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
this.manager.email
|
||||
await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
ctx.manager.email
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.Modules.promises.hooks.fire,
|
||||
ctx.Modules.promises.hooks.fire,
|
||||
'sendGroupSSOReminder',
|
||||
this.manager._id,
|
||||
this.subscription._id
|
||||
ctx.manager._id,
|
||||
ctx.subscription._id
|
||||
)
|
||||
})
|
||||
|
||||
it('does not send an SSO invite if SSO is disabled and inviting self', async function () {
|
||||
this.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
|
||||
this.SSOConfig.findById
|
||||
.withArgs(this.subscription.ssoConfig)
|
||||
it('does not send an SSO invite if SSO is disabled and inviting self', async function (ctx) {
|
||||
ctx.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
|
||||
ctx.SSOConfig.findById
|
||||
.withArgs(ctx.subscription.ssoConfig)
|
||||
.resolves({ enabled: false })
|
||||
|
||||
await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
this.manager.email
|
||||
await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
ctx.manager.email
|
||||
)
|
||||
sinon.assert.notCalled(this.Modules.promises.hooks.fire)
|
||||
sinon.assert.notCalled(ctx.Modules.promises.hooks.fire)
|
||||
})
|
||||
|
||||
it('sends a notification if inviting registered user', async function () {
|
||||
it('sends a notification if inviting registered user', async function (ctx) {
|
||||
const id = new ObjectId('6a6b3a8014829a865bbf700d')
|
||||
const managedUsersEnabled = false
|
||||
|
||||
this.UserGetter.promises.getUserByMainEmail
|
||||
ctx.UserGetter.promises.getUserByMainEmail
|
||||
.withArgs('john.snow@example.com')
|
||||
.resolves({
|
||||
_id: id,
|
||||
})
|
||||
|
||||
const invite = await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
const invite = await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
'John.Snow@example.com'
|
||||
)
|
||||
this.NotificationsBuilder.promises
|
||||
ctx.NotificationsBuilder.promises
|
||||
.groupInvitation(
|
||||
id.toString(),
|
||||
this.subscription._id,
|
||||
ctx.subscription._id,
|
||||
managedUsersEnabled
|
||||
)
|
||||
.create.calledWith(invite)
|
||||
@@ -309,42 +368,42 @@ describe('TeamInvitesHandler', function () {
|
||||
})
|
||||
|
||||
describe('importInvite', function () {
|
||||
beforeEach(function () {
|
||||
this.sentAt = new Date()
|
||||
beforeEach(function (ctx) {
|
||||
ctx.sentAt = new Date()
|
||||
})
|
||||
|
||||
it('can imports an invite from v1', function () {
|
||||
this.TeamInvitesHandler.importInvite(
|
||||
this.subscription,
|
||||
it('can imports an invite from v1', function (ctx) {
|
||||
ctx.TeamInvitesHandler.importInvite(
|
||||
ctx.subscription,
|
||||
'A-Team',
|
||||
'hannibal@a-team.org',
|
||||
'secret',
|
||||
this.sentAt,
|
||||
ctx.sentAt,
|
||||
error => {
|
||||
expect(error).not.to.exist
|
||||
|
||||
this.subscription.save.calledOnce.should.eq(true)
|
||||
ctx.subscription.save.calledOnce.should.eq(true)
|
||||
|
||||
const invite = this.subscription.teamInvites.find(
|
||||
const invite = ctx.subscription.teamInvites.find(
|
||||
i => i.email === 'hannibal@a-team.org'
|
||||
)
|
||||
expect(invite.token).to.eq('secret')
|
||||
expect(invite.sentAt).to.eq(this.sentAt)
|
||||
expect(invite.sentAt).to.eq(ctx.sentAt)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('acceptInvite', function () {
|
||||
beforeEach(function () {
|
||||
this.user = {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.user = {
|
||||
id: '123456789',
|
||||
first_name: 'Tyrion',
|
||||
last_name: 'Lannister',
|
||||
email: 'tyrion@example.com',
|
||||
}
|
||||
|
||||
this.user_subscription = {
|
||||
ctx.user_subscription = {
|
||||
id: '66264b9125930b976cc0811e',
|
||||
_id: new ObjectId('66264b9125930b976cc0811e'),
|
||||
groupPlan: false,
|
||||
@@ -355,17 +414,17 @@ describe('TeamInvitesHandler', function () {
|
||||
save: sinon.stub().resolves(),
|
||||
}
|
||||
|
||||
this.ipAddress = '127.0.0.1'
|
||||
ctx.ipAddress = '127.0.0.1'
|
||||
|
||||
this.UserGetter.promises.getUserByAnyEmail
|
||||
.withArgs(this.user.email)
|
||||
.resolves(this.user)
|
||||
ctx.UserGetter.promises.getUserByAnyEmail
|
||||
.withArgs(ctx.user.email)
|
||||
.resolves(ctx.user)
|
||||
|
||||
this.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(this.user.id)
|
||||
.resolves(this.user_subscription)
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(ctx.user.id)
|
||||
.resolves(ctx.user_subscription)
|
||||
|
||||
this.subscription.teamInvites.push({
|
||||
ctx.subscription.teamInvites.push({
|
||||
email: 'john.snow@example.com',
|
||||
token: 'dddddddd',
|
||||
inviterName: 'Daenerys Targaryen (daenerys@example.com)',
|
||||
@@ -373,24 +432,24 @@ describe('TeamInvitesHandler', function () {
|
||||
})
|
||||
|
||||
describe('with standard group', function () {
|
||||
it('adds the user to the team', async function () {
|
||||
await this.TeamInvitesHandler.promises.acceptInvite(
|
||||
it('adds the user to the team', async function (ctx) {
|
||||
await ctx.TeamInvitesHandler.promises.acceptInvite(
|
||||
'dddddddd',
|
||||
this.user.id,
|
||||
this.ipAddress
|
||||
ctx.user.id,
|
||||
ctx.ipAddress
|
||||
)
|
||||
this.SubscriptionUpdater.promises.addUserToGroup
|
||||
.calledWith(this.subscription._id, this.user.id)
|
||||
ctx.SubscriptionUpdater.promises.addUserToGroup
|
||||
.calledWith(ctx.subscription._id, ctx.user.id)
|
||||
.should.eq(true)
|
||||
})
|
||||
|
||||
it('removes the invite from the subscription', async function () {
|
||||
await this.TeamInvitesHandler.promises.acceptInvite(
|
||||
it('removes the invite from the subscription', async function (ctx) {
|
||||
await ctx.TeamInvitesHandler.promises.acceptInvite(
|
||||
'dddddddd',
|
||||
this.user.id,
|
||||
this.ipAddress
|
||||
ctx.user.id,
|
||||
ctx.ipAddress
|
||||
)
|
||||
this.Subscription.updateOne
|
||||
ctx.Subscription.updateOne
|
||||
.calledWith(
|
||||
{ _id: new ObjectId('55153a8014829a865bbf700d') },
|
||||
{ $pull: { teamInvites: { email: 'john.snow@example.com' } } }
|
||||
@@ -398,114 +457,114 @@ describe('TeamInvitesHandler', function () {
|
||||
.should.eq(true)
|
||||
})
|
||||
|
||||
it('removes dashboard notification after they accepted group invitation', async function () {
|
||||
it('removes dashboard notification after they accepted group invitation', async function (ctx) {
|
||||
const managedUsersEnabled = false
|
||||
|
||||
await this.TeamInvitesHandler.promises.acceptInvite(
|
||||
await ctx.TeamInvitesHandler.promises.acceptInvite(
|
||||
'dddddddd',
|
||||
this.user.id,
|
||||
this.ipAddress
|
||||
ctx.user.id,
|
||||
ctx.ipAddress
|
||||
)
|
||||
sinon.assert.called(
|
||||
this.NotificationsBuilder.promises.groupInvitation(
|
||||
this.user.id,
|
||||
this.subscription._id,
|
||||
ctx.NotificationsBuilder.promises.groupInvitation(
|
||||
ctx.user.id,
|
||||
ctx.subscription._id,
|
||||
managedUsersEnabled
|
||||
).read
|
||||
)
|
||||
})
|
||||
|
||||
it('should not schedule an SSO invite reminder', async function () {
|
||||
await this.TeamInvitesHandler.promises.acceptInvite(
|
||||
it('should not schedule an SSO invite reminder', async function (ctx) {
|
||||
await ctx.TeamInvitesHandler.promises.acceptInvite(
|
||||
'dddddddd',
|
||||
this.user.id,
|
||||
this.ipAddress
|
||||
ctx.user.id,
|
||||
ctx.ipAddress
|
||||
)
|
||||
sinon.assert.notCalled(this.Modules.promises.hooks.fire)
|
||||
sinon.assert.notCalled(ctx.Modules.promises.hooks.fire)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with managed group', function () {
|
||||
it('should enroll the group member', async function () {
|
||||
this.subscription.managedUsersEnabled = true
|
||||
it('should enroll the group member', async function (ctx) {
|
||||
ctx.subscription.managedUsersEnabled = true
|
||||
|
||||
await this.TeamInvitesHandler.promises.acceptInvite(
|
||||
await ctx.TeamInvitesHandler.promises.acceptInvite(
|
||||
'dddddddd',
|
||||
this.user.id,
|
||||
this.ipAddress
|
||||
ctx.user.id,
|
||||
ctx.ipAddress
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.SubscriptionUpdater.promises.deleteSubscription,
|
||||
this.user_subscription,
|
||||
{ id: this.user.id, ip: this.ipAddress }
|
||||
ctx.SubscriptionUpdater.promises.deleteSubscription,
|
||||
ctx.user_subscription,
|
||||
{ id: ctx.user.id, ip: ctx.ipAddress }
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.RecurlyClient.promises.terminateSubscriptionByUuid,
|
||||
this.user_subscription.recurlySubscription_id
|
||||
ctx.RecurlyClient.promises.terminateSubscriptionByUuid,
|
||||
ctx.user_subscription.recurlySubscription_id
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.Modules.promises.hooks.fire,
|
||||
ctx.Modules.promises.hooks.fire,
|
||||
'enrollInManagedSubscription',
|
||||
this.user.id,
|
||||
this.subscription
|
||||
ctx.user.id,
|
||||
ctx.subscription
|
||||
)
|
||||
})
|
||||
|
||||
it('should not delete the users subscription if that subscription is also the join target', async function () {
|
||||
this.subscription.managedUsersEnabled = true
|
||||
this.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(this.user.id)
|
||||
.resolves(this.subscription)
|
||||
it('should not delete the users subscription if that subscription is also the join target', async function (ctx) {
|
||||
ctx.subscription.managedUsersEnabled = true
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(ctx.user.id)
|
||||
.resolves(ctx.subscription)
|
||||
|
||||
await this.TeamInvitesHandler.promises.acceptInvite(
|
||||
await ctx.TeamInvitesHandler.promises.acceptInvite(
|
||||
'dddddddd',
|
||||
this.user.id,
|
||||
this.ipAddress
|
||||
ctx.user.id,
|
||||
ctx.ipAddress
|
||||
)
|
||||
|
||||
sinon.assert.notCalled(
|
||||
this.SubscriptionUpdater.promises.deleteSubscription
|
||||
ctx.SubscriptionUpdater.promises.deleteSubscription
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with group SSO enabled', function () {
|
||||
it('should schedule an SSO invite reminder', async function () {
|
||||
this.subscription.ssoConfig = 'ssoconfig1'
|
||||
this.SSOConfig.findById
|
||||
it('should schedule an SSO invite reminder', async function (ctx) {
|
||||
ctx.subscription.ssoConfig = 'ssoconfig1'
|
||||
ctx.SSOConfig.findById
|
||||
.withArgs('ssoconfig1')
|
||||
.resolves({ enabled: true })
|
||||
|
||||
await this.TeamInvitesHandler.promises.acceptInvite(
|
||||
await ctx.TeamInvitesHandler.promises.acceptInvite(
|
||||
'dddddddd',
|
||||
this.user.id,
|
||||
this.ipAddress
|
||||
ctx.user.id,
|
||||
ctx.ipAddress
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.Modules.promises.hooks.fire,
|
||||
ctx.Modules.promises.hooks.fire,
|
||||
'scheduleGroupSSOReminder',
|
||||
this.user.id,
|
||||
this.subscription._id
|
||||
ctx.user.id,
|
||||
ctx.subscription._id
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('revokeInvite', function () {
|
||||
it('removes the team invite from the subscription', async function () {
|
||||
await this.TeamInvitesHandler.promises.revokeInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
it('removes the team invite from the subscription', async function (ctx) {
|
||||
await ctx.TeamInvitesHandler.promises.revokeInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
'jorah@example.com'
|
||||
)
|
||||
this.Subscription.updateOne
|
||||
ctx.Subscription.updateOne
|
||||
.calledWith(
|
||||
{ _id: new ObjectId('55153a8014829a865bbf700d') },
|
||||
{ $pull: { teamInvites: { email: 'jorah@example.com' } } }
|
||||
)
|
||||
.should.eq(true)
|
||||
|
||||
this.Subscription.updateOne
|
||||
ctx.Subscription.updateOne
|
||||
.calledWith(
|
||||
{ _id: new ObjectId('55153a8014829a865bbf700d') },
|
||||
{ $pull: { invited_emails: 'jorah@example.com' } }
|
||||
@@ -513,7 +572,7 @@ describe('TeamInvitesHandler', function () {
|
||||
.should.eq(true)
|
||||
})
|
||||
|
||||
it('removes dashboard notification for pending group invitation', async function () {
|
||||
it('removes dashboard notification for pending group invitation', async function (ctx) {
|
||||
const managedUsersEnabled = false
|
||||
|
||||
const pendingUser = {
|
||||
@@ -521,20 +580,20 @@ describe('TeamInvitesHandler', function () {
|
||||
email: 'tyrion@example.com',
|
||||
}
|
||||
|
||||
this.UserGetter.promises.getUserByAnyEmail
|
||||
ctx.UserGetter.promises.getUserByAnyEmail
|
||||
.withArgs(pendingUser.email)
|
||||
.resolves(pendingUser)
|
||||
|
||||
await this.TeamInvitesHandler.promises.revokeInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
await ctx.TeamInvitesHandler.promises.revokeInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
pendingUser.email
|
||||
)
|
||||
|
||||
sinon.assert.called(
|
||||
this.NotificationsBuilder.promises.groupInvitation(
|
||||
ctx.NotificationsBuilder.promises.groupInvitation(
|
||||
pendingUser.id,
|
||||
this.subscription._id,
|
||||
ctx.subscription._id,
|
||||
managedUsersEnabled
|
||||
).read
|
||||
)
|
||||
@@ -542,47 +601,47 @@ describe('TeamInvitesHandler', function () {
|
||||
})
|
||||
|
||||
describe('createTeamInvitesForLegacyInvitedEmail', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription.invited_emails = [
|
||||
beforeEach(function (ctx) {
|
||||
ctx.subscription.invited_emails = [
|
||||
'eddard@example.com',
|
||||
'robert@example.com',
|
||||
]
|
||||
this.TeamInvitesHandler.createInvite = sinon.stub().resolves(null)
|
||||
this.SubscriptionLocator.promises.getGroupsWithEmailInvite = sinon
|
||||
ctx.TeamInvitesHandler.createInvite = sinon.stub().resolves(null)
|
||||
ctx.SubscriptionLocator.promises.getGroupsWithEmailInvite = sinon
|
||||
.stub()
|
||||
.resolves([this.subscription])
|
||||
.resolves([ctx.subscription])
|
||||
})
|
||||
|
||||
it('sends an invitation email to addresses in the legacy invited_emails field', async function () {
|
||||
it('sends an invitation email to addresses in the legacy invited_emails field', async function (ctx) {
|
||||
const invites =
|
||||
await this.TeamInvitesHandler.promises.createTeamInvitesForLegacyInvitedEmail(
|
||||
await ctx.TeamInvitesHandler.promises.createTeamInvitesForLegacyInvitedEmail(
|
||||
'eddard@example.com'
|
||||
)
|
||||
|
||||
expect(invites.length).to.eq(1)
|
||||
|
||||
const [invite] = invites
|
||||
expect(invite.token).to.eq(this.newToken)
|
||||
expect(invite.token).to.eq(ctx.newToken)
|
||||
expect(invite.email).to.eq('eddard@example.com')
|
||||
expect(invite.inviterName).to.eq(
|
||||
'Daenerys Targaryen (daenerys@example.com)'
|
||||
)
|
||||
expect(invite.invite).to.be.true
|
||||
expect(this.subscription.teamInvites).to.deep.include(invite)
|
||||
expect(ctx.subscription.teamInvites).to.deep.include(invite)
|
||||
})
|
||||
})
|
||||
|
||||
describe('validation', function () {
|
||||
it("doesn't create an invite if the team limit has been reached", async function () {
|
||||
this.LimitationsManager.teamHasReachedMemberLimit = sinon
|
||||
it("doesn't create an invite if the team limit has been reached", async function (ctx) {
|
||||
ctx.LimitationsManager.teamHasReachedMemberLimit = sinon
|
||||
.stub()
|
||||
.returns(true)
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
'John.Snow@example.com'
|
||||
)
|
||||
} catch (err) {
|
||||
@@ -596,14 +655,14 @@ describe('TeamInvitesHandler', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it("doesn't create an invite if the subscription is not in a group plan", async function () {
|
||||
this.subscription.groupPlan = false
|
||||
it("doesn't create an invite if the subscription is not in a group plan", async function (ctx) {
|
||||
ctx.subscription.groupPlan = false
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
'John.Snow@example.com'
|
||||
)
|
||||
} catch (err) {
|
||||
@@ -617,24 +676,24 @@ describe('TeamInvitesHandler', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it("doesn't create an invite if the user is already part of the team", async function () {
|
||||
it("doesn't create an invite if the user is already part of the team", async function (ctx) {
|
||||
const member = {
|
||||
id: '1a2b',
|
||||
_id: '1a2b',
|
||||
email: 'tyrion@example.com',
|
||||
}
|
||||
|
||||
this.subscription.member_ids = [member.id]
|
||||
this.UserGetter.promises.getUserByAnyEmail
|
||||
ctx.subscription.member_ids = [member.id]
|
||||
ctx.UserGetter.promises.getUserByAnyEmail
|
||||
.withArgs(member.email)
|
||||
.resolves(member)
|
||||
|
||||
let error
|
||||
|
||||
try {
|
||||
await this.TeamInvitesHandler.promises.createInvite(
|
||||
this.manager._id,
|
||||
this.subscription,
|
||||
await ctx.TeamInvitesHandler.promises.createInvite(
|
||||
ctx.manager._id,
|
||||
ctx.subscription,
|
||||
'tyrion@example.com'
|
||||
)
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,236 +1,251 @@
|
||||
/* eslint-disable
|
||||
max-len,
|
||||
no-return-assign,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { RequestFailedError } = require('@overleaf/fetch-utils')
|
||||
const { ReadableString } = require('@overleaf/stream-utils')
|
||||
import { beforeEach, describe, it, vi } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import { RequestFailedError } from '@overleaf/fetch-utils'
|
||||
import { ReadableString } from '@overleaf/stream-utils'
|
||||
|
||||
const modulePath = '../../../../app/src/Features/Templates/TemplatesManager'
|
||||
|
||||
describe('TemplatesManager', function () {
|
||||
beforeEach(function () {
|
||||
this.project_id = 'project-id'
|
||||
this.brandVariationId = 'brand-variation-id'
|
||||
this.compiler = 'pdflatex'
|
||||
this.imageName = 'TL2017'
|
||||
this.mainFile = 'main.tex'
|
||||
this.templateId = 'template-id'
|
||||
this.templateName = 'template name'
|
||||
this.templateVersionId = 'template-version-id'
|
||||
this.user_id = 'user-id'
|
||||
this.dumpPath = `${this.dumpFolder}/${this.uuid}`
|
||||
this.callback = sinon.stub()
|
||||
this.pipeline = sinon.stub().callsFake(async (stream, res) => {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.project_id = 'project-id'
|
||||
ctx.brandVariationId = 'brand-variation-id'
|
||||
ctx.compiler = 'pdflatex'
|
||||
ctx.imageName = 'TL2017'
|
||||
ctx.mainFile = 'main.tex'
|
||||
ctx.templateId = 'template-id'
|
||||
ctx.templateName = 'template name'
|
||||
ctx.templateVersionId = 'template-version-id'
|
||||
ctx.user_id = 'user-id'
|
||||
ctx.dumpFolder = 'dump/path'
|
||||
ctx.uuid = '1234'
|
||||
ctx.dumpPath = `${ctx.dumpFolder}/${ctx.uuid}`
|
||||
ctx.callback = sinon.stub()
|
||||
ctx.pipeline = sinon.stub().callsFake(async (stream, res) => {
|
||||
if (res.callback) res.callback()
|
||||
})
|
||||
this.request = sinon.stub().returns({
|
||||
ctx.request = sinon.stub().returns({
|
||||
pipe() {},
|
||||
on() {},
|
||||
response: {
|
||||
statusCode: 200,
|
||||
},
|
||||
})
|
||||
this.fs = {
|
||||
ctx.fs = {
|
||||
promises: { unlink: sinon.stub() },
|
||||
unlink: sinon.stub(),
|
||||
createWriteStream: sinon.stub().returns({ on: sinon.stub().yields() }),
|
||||
}
|
||||
this.ProjectUploadManager = {
|
||||
ctx.ProjectUploadManager = {
|
||||
promises: {
|
||||
createProjectFromZipArchiveWithName: sinon
|
||||
.stub()
|
||||
.resolves({ _id: this.project_id }),
|
||||
.resolves({ _id: ctx.project_id }),
|
||||
},
|
||||
}
|
||||
this.dumpFolder = 'dump/path'
|
||||
this.ProjectOptionsHandler = {
|
||||
ctx.ProjectOptionsHandler = {
|
||||
promises: {
|
||||
setCompiler: sinon.stub().resolves(),
|
||||
setImageName: sinon.stub().resolves(),
|
||||
setBrandVariationId: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.uuid = '1234'
|
||||
this.ProjectRootDocManager = {
|
||||
ctx.ProjectRootDocManager = {
|
||||
promises: {
|
||||
setRootDocFromName: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.ProjectDetailsHandler = {
|
||||
ctx.ProjectDetailsHandler = {
|
||||
getProjectDescription: sinon.stub(),
|
||||
fixProjectName: sinon.stub().returns(this.templateName),
|
||||
fixProjectName: sinon.stub().returns(ctx.templateName),
|
||||
}
|
||||
this.Project = { updateOne: sinon.stub().resolves() }
|
||||
this.mockStream = new ReadableString('{}')
|
||||
this.mockResponse = {
|
||||
ctx.Project = { updateOne: sinon.stub().resolves() }
|
||||
ctx.mockStream = new ReadableString('{}')
|
||||
ctx.mockResponse = {
|
||||
status: 200,
|
||||
headers: new Headers({
|
||||
'Content-Length': '2',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
}
|
||||
this.FetchUtils = {
|
||||
ctx.FetchUtils = {
|
||||
fetchJson: sinon.stub(),
|
||||
fetchStreamWithResponse: sinon.stub().resolves({
|
||||
stream: this.mockStream,
|
||||
response: this.mockResponse,
|
||||
stream: ctx.mockStream,
|
||||
response: ctx.mockResponse,
|
||||
}),
|
||||
RequestFailedError,
|
||||
}
|
||||
this.TemplatesManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'@overleaf/fetch-utils': this.FetchUtils,
|
||||
'../Uploads/ProjectUploadManager': this.ProjectUploadManager,
|
||||
'../Project/ProjectOptionsHandler': this.ProjectOptionsHandler,
|
||||
'../Project/ProjectRootDocManager': this.ProjectRootDocManager,
|
||||
'../Project/ProjectDetailsHandler': this.ProjectDetailsHandler,
|
||||
'../Authentication/SessionManager': (this.SessionManager = {
|
||||
getLoggedInUserId: sinon.stub(),
|
||||
}),
|
||||
'@overleaf/settings': {
|
||||
path: {
|
||||
dumpFolder: this.dumpFolder,
|
||||
},
|
||||
siteUrl: (this.siteUrl = 'http://127.0.0.1:3000'),
|
||||
apis: {
|
||||
v1: {
|
||||
url: (this.v1Url = 'http://overleaf.com'),
|
||||
user: 'overleaf',
|
||||
pass: 'password',
|
||||
timeout: 10,
|
||||
},
|
||||
},
|
||||
overleaf: {
|
||||
host: this.v1Url,
|
||||
vi.doMock('@overleaf/fetch-utils', () => ctx.FetchUtils)
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Uploads/ProjectUploadManager',
|
||||
() => ({ default: ctx.ProjectUploadManager })
|
||||
)
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectOptionsHandler',
|
||||
() => ({ default: ctx.ProjectOptionsHandler })
|
||||
)
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectRootDocManager',
|
||||
() => ({ default: ctx.ProjectRootDocManager })
|
||||
)
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectDetailsHandler',
|
||||
() => ({ default: ctx.ProjectDetailsHandler })
|
||||
)
|
||||
|
||||
ctx.SessionManager = {
|
||||
getLoggedInUserId: sinon.stub(),
|
||||
}
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Authentication/SessionManager',
|
||||
() => ({ default: ctx.SessionManager })
|
||||
)
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: {
|
||||
path: {
|
||||
dumpFolder: ctx.dumpFolder,
|
||||
},
|
||||
siteUrl: (ctx.siteUrl = 'http://127.0.0.1:3000'),
|
||||
apis: {
|
||||
v1: {
|
||||
url: (ctx.v1Url = 'http://overleaf.com'),
|
||||
user: 'overleaf',
|
||||
pass: 'password',
|
||||
timeout: 10,
|
||||
},
|
||||
},
|
||||
crypto: {
|
||||
randomUUID: () => this.uuid,
|
||||
},
|
||||
request: this.request,
|
||||
fs: this.fs,
|
||||
'../../models/Project': { Project: this.Project },
|
||||
'stream/promises': { pipeline: this.pipeline },
|
||||
'../Compile/ClsiCacheManager': {
|
||||
prepareClsiCache: sinon.stub().rejects(new Error('ignore this')),
|
||||
overleaf: {
|
||||
host: ctx.v1Url,
|
||||
},
|
||||
},
|
||||
}).promises
|
||||
return (this.zipUrl =
|
||||
'%2Ftemplates%2F52fb86a81ae1e566597a25f6%2Fv%2F4%2Fzip&templateName=Moderncv%20Banking&compiler=pdflatex')
|
||||
}))
|
||||
|
||||
vi.doMock('node:crypto', () => ({
|
||||
default: {
|
||||
randomUUID: () => ctx.uuid,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.doMock('node:fs', () => ({ default: ctx.fs }))
|
||||
|
||||
vi.doMock('request', () => ({ default: ctx.request }))
|
||||
|
||||
vi.doMock('../../../../app/src/models/Project', () => ({
|
||||
Project: ctx.Project,
|
||||
}))
|
||||
|
||||
vi.doMock('node:stream/promises', () => ({ pipeline: ctx.pipeline }))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Compile/ClsiCacheManager', () => ({
|
||||
default: {
|
||||
prepareClsiCache: sinon.stub().rejects(new Error('ignore this')),
|
||||
},
|
||||
}))
|
||||
|
||||
ctx.TemplatesManager = (await import(modulePath)).default.promises
|
||||
ctx.zipUrl =
|
||||
'%2Ftemplates%2F52fb86a81ae1e566597a25f6%2Fv%2F4%2Fzip&templateName=Moderncv%20Banking&compiler=pdflatex'
|
||||
})
|
||||
|
||||
describe('createProjectFromV1Template', function () {
|
||||
describe('when all options passed', function () {
|
||||
beforeEach(function () {
|
||||
return this.TemplatesManager.createProjectFromV1Template(
|
||||
this.brandVariationId,
|
||||
this.compiler,
|
||||
this.mainFile,
|
||||
this.templateId,
|
||||
this.templateName,
|
||||
this.templateVersionId,
|
||||
this.user_id,
|
||||
this.imageName
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.TemplatesManager.createProjectFromV1Template(
|
||||
ctx.brandVariationId,
|
||||
ctx.compiler,
|
||||
ctx.mainFile,
|
||||
ctx.templateId,
|
||||
ctx.templateName,
|
||||
ctx.templateVersionId,
|
||||
ctx.user_id,
|
||||
ctx.imageName
|
||||
)
|
||||
})
|
||||
|
||||
it('should fetch zip from v1 based on template id', function () {
|
||||
return this.FetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
|
||||
`${this.v1Url}/api/v1/overleaf/templates/${this.templateVersionId}`
|
||||
it('should fetch zip from v1 based on template id', function (ctx) {
|
||||
ctx.FetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
|
||||
`${ctx.v1Url}/api/v1/overleaf/templates/${ctx.templateVersionId}`
|
||||
)
|
||||
})
|
||||
|
||||
it('should save temporary file', function () {
|
||||
return this.fs.createWriteStream.should.have.been.calledWith(
|
||||
this.dumpPath
|
||||
)
|
||||
it('should save temporary file', function (ctx) {
|
||||
ctx.fs.createWriteStream.should.have.been.calledWith(ctx.dumpPath)
|
||||
})
|
||||
|
||||
it('should create project', function () {
|
||||
return this.ProjectUploadManager.promises.createProjectFromZipArchiveWithName.should.have.been.calledWithMatch(
|
||||
this.user_id,
|
||||
this.templateName,
|
||||
this.dumpPath,
|
||||
it('should create project', function (ctx) {
|
||||
ctx.ProjectUploadManager.promises.createProjectFromZipArchiveWithName.should.have.been.calledWithMatch(
|
||||
ctx.user_id,
|
||||
ctx.templateName,
|
||||
ctx.dumpPath,
|
||||
{
|
||||
fromV1TemplateId: this.templateId,
|
||||
fromV1TemplateVersionId: this.templateVersionId,
|
||||
fromV1TemplateId: ctx.templateId,
|
||||
fromV1TemplateVersionId: ctx.templateVersionId,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should unlink file', function () {
|
||||
return this.fs.promises.unlink.should.have.been.calledWith(
|
||||
this.dumpPath
|
||||
it('should unlink file', function (ctx) {
|
||||
ctx.fs.promises.unlink.should.have.been.calledWith(ctx.dumpPath)
|
||||
})
|
||||
|
||||
it('should set project options when passed', function (ctx) {
|
||||
ctx.ProjectOptionsHandler.promises.setCompiler.should.have.been.calledWithMatch(
|
||||
ctx.project_id,
|
||||
ctx.compiler
|
||||
)
|
||||
ctx.ProjectOptionsHandler.promises.setImageName.should.have.been.calledWithMatch(
|
||||
ctx.project_id,
|
||||
ctx.imageName
|
||||
)
|
||||
ctx.ProjectRootDocManager.promises.setRootDocFromName.should.have.been.calledWithMatch(
|
||||
ctx.project_id,
|
||||
ctx.mainFile
|
||||
)
|
||||
ctx.ProjectOptionsHandler.promises.setBrandVariationId.should.have.been.calledWithMatch(
|
||||
ctx.project_id,
|
||||
ctx.brandVariationId
|
||||
)
|
||||
})
|
||||
|
||||
it('should set project options when passed', function () {
|
||||
this.ProjectOptionsHandler.promises.setCompiler.should.have.been.calledWithMatch(
|
||||
this.project_id,
|
||||
this.compiler
|
||||
)
|
||||
this.ProjectOptionsHandler.promises.setImageName.should.have.been.calledWithMatch(
|
||||
this.project_id,
|
||||
this.imageName
|
||||
)
|
||||
this.ProjectRootDocManager.promises.setRootDocFromName.should.have.been.calledWithMatch(
|
||||
this.project_id,
|
||||
this.mainFile
|
||||
)
|
||||
return this.ProjectOptionsHandler.promises.setBrandVariationId.should.have.been.calledWithMatch(
|
||||
this.project_id,
|
||||
this.brandVariationId
|
||||
)
|
||||
})
|
||||
|
||||
it('should update project', function () {
|
||||
return this.Project.updateOne.should.have.been.calledWithMatch(
|
||||
{ _id: this.project_id },
|
||||
it('should update project', function (ctx) {
|
||||
ctx.Project.updateOne.should.have.been.calledWithMatch(
|
||||
{ _id: ctx.project_id },
|
||||
{
|
||||
fromV1TemplateId: this.templateId,
|
||||
fromV1TemplateVersionId: this.templateVersionId,
|
||||
fromV1TemplateId: ctx.templateId,
|
||||
fromV1TemplateVersionId: ctx.templateVersionId,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when some options not set', function () {
|
||||
beforeEach(function () {
|
||||
return this.TemplatesManager.createProjectFromV1Template(
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.TemplatesManager.createProjectFromV1Template(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
this.templateId,
|
||||
this.templateName,
|
||||
this.templateVersionId,
|
||||
this.user_id,
|
||||
ctx.templateId,
|
||||
ctx.templateName,
|
||||
ctx.templateVersionId,
|
||||
ctx.user_id,
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
it('should not set missing project options', function () {
|
||||
this.ProjectOptionsHandler.promises.setCompiler.called.should.equal(
|
||||
it('should not set missing project options', function (ctx) {
|
||||
ctx.ProjectOptionsHandler.promises.setCompiler.called.should.equal(
|
||||
false
|
||||
)
|
||||
this.ProjectRootDocManager.promises.setRootDocFromName.called.should.equal(
|
||||
ctx.ProjectRootDocManager.promises.setRootDocFromName.called.should.equal(
|
||||
false
|
||||
)
|
||||
this.ProjectOptionsHandler.promises.setBrandVariationId.called.should.equal(
|
||||
ctx.ProjectOptionsHandler.promises.setBrandVariationId.called.should.equal(
|
||||
false
|
||||
)
|
||||
return this.ProjectOptionsHandler.promises.setImageName.should.have.been.calledWithMatch(
|
||||
this.project_id,
|
||||
ctx.ProjectOptionsHandler.promises.setImageName.should.have.been.calledWithMatch(
|
||||
ctx.project_id,
|
||||
'wl_texlive:2018.1'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,176 +1,194 @@
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
const { Project } = require('../helpers/models/Project')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import indirectlyImportModels from '../helpers/indirectlyImportModels.js'
|
||||
|
||||
const { Project } = indirectlyImportModels(['Project'])
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/TpdsProjectFlusher'
|
||||
|
||||
describe('TpdsProjectFlusher', function () {
|
||||
beforeEach(function () {
|
||||
this.project = { _id: new ObjectId(), overleaf: { history: { id: 42 } } }
|
||||
this.folder = { _id: new ObjectId() }
|
||||
this.docs = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.project = { _id: new ObjectId(), overleaf: { history: { id: 42 } } }
|
||||
ctx.folder = { _id: new ObjectId() }
|
||||
ctx.docs = {
|
||||
'/doc/one': {
|
||||
_id: 'mock-doc-1',
|
||||
lines: ['one'],
|
||||
rev: 5,
|
||||
folder: this.folder,
|
||||
folder: ctx.folder,
|
||||
},
|
||||
'/doc/two': {
|
||||
_id: 'mock-doc-2',
|
||||
lines: ['two'],
|
||||
rev: 6,
|
||||
folder: this.folder,
|
||||
folder: ctx.folder,
|
||||
},
|
||||
}
|
||||
this.files = {
|
||||
ctx.files = {
|
||||
'/file/one': {
|
||||
_id: 'mock-file-1',
|
||||
rev: 7,
|
||||
folder: this.folder,
|
||||
folder: ctx.folder,
|
||||
hash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
},
|
||||
'/file/two': {
|
||||
_id: 'mock-file-2',
|
||||
rev: 8,
|
||||
folder: this.folder,
|
||||
folder: ctx.folder,
|
||||
hash: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
},
|
||||
}
|
||||
this.DocumentUpdaterHandler = {
|
||||
ctx.DocumentUpdaterHandler = {
|
||||
promises: {
|
||||
flushProjectToMongo: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.ProjectGetter = {
|
||||
ctx.ProjectGetter = {
|
||||
promises: {
|
||||
getProject: sinon.stub().resolves(this.project),
|
||||
getProject: sinon.stub().resolves(ctx.project),
|
||||
},
|
||||
}
|
||||
this.ProjectEntityHandler = {
|
||||
ctx.ProjectEntityHandler = {
|
||||
promises: {
|
||||
getAllDocs: sinon.stub().withArgs(this.project._id).resolves(this.docs),
|
||||
getAllFiles: sinon
|
||||
.stub()
|
||||
.withArgs(this.project._id)
|
||||
.resolves(this.files),
|
||||
getAllDocs: sinon.stub().withArgs(ctx.project._id).resolves(ctx.docs),
|
||||
getAllFiles: sinon.stub().withArgs(ctx.project._id).resolves(ctx.files),
|
||||
},
|
||||
}
|
||||
this.TpdsUpdateSender = {
|
||||
ctx.TpdsUpdateSender = {
|
||||
promises: {
|
||||
addDoc: sinon.stub().resolves(),
|
||||
addFile: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.ProjectMock = sinon.mock(Project)
|
||||
ctx.ProjectMock = sinon.mock(Project)
|
||||
|
||||
this.TpdsProjectFlusher = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'../DocumentUpdater/DocumentUpdaterHandler':
|
||||
this.DocumentUpdaterHandler,
|
||||
'../Project/ProjectGetter': this.ProjectGetter,
|
||||
'../Project/ProjectEntityHandler': this.ProjectEntityHandler,
|
||||
'../../models/Project': { Project },
|
||||
'./TpdsUpdateSender': this.TpdsUpdateSender,
|
||||
},
|
||||
})
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler',
|
||||
() => ({
|
||||
default: ctx.DocumentUpdaterHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
|
||||
default: ctx.ProjectGetter,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectEntityHandler',
|
||||
() => ({
|
||||
default: ctx.ProjectEntityHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/models/Project', () => ({
|
||||
Project,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender',
|
||||
() => ({
|
||||
default: ctx.TpdsUpdateSender,
|
||||
})
|
||||
)
|
||||
|
||||
ctx.TpdsProjectFlusher = (await import(MODULE_PATH)).default
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.ProjectMock.restore()
|
||||
afterEach(function (ctx) {
|
||||
ctx.ProjectMock.restore()
|
||||
})
|
||||
|
||||
describe('flushProjectToTpds', function () {
|
||||
describe('usually', function () {
|
||||
beforeEach(async function () {
|
||||
await this.TpdsProjectFlusher.promises.flushProjectToTpds(
|
||||
this.project._id
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.TpdsProjectFlusher.promises.flushProjectToTpds(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
|
||||
it('should flush the project from the doc updater', function () {
|
||||
it('should flush the project from the doc updater', function (ctx) {
|
||||
expect(
|
||||
this.DocumentUpdaterHandler.promises.flushProjectToMongo
|
||||
).to.have.been.calledWith(this.project._id)
|
||||
ctx.DocumentUpdaterHandler.promises.flushProjectToMongo
|
||||
).to.have.been.calledWith(ctx.project._id)
|
||||
})
|
||||
|
||||
it('should flush each doc to the TPDS', function () {
|
||||
for (const [path, doc] of Object.entries(this.docs)) {
|
||||
expect(this.TpdsUpdateSender.promises.addDoc).to.have.been.calledWith(
|
||||
{
|
||||
projectId: this.project._id,
|
||||
docId: doc._id,
|
||||
projectName: this.project.name,
|
||||
rev: doc.rev,
|
||||
path,
|
||||
folderId: this.folder._id,
|
||||
}
|
||||
)
|
||||
it('should flush each doc to the TPDS', function (ctx) {
|
||||
for (const [path, doc] of Object.entries(ctx.docs)) {
|
||||
expect(ctx.TpdsUpdateSender.promises.addDoc).to.have.been.calledWith({
|
||||
projectId: ctx.project._id,
|
||||
docId: doc._id,
|
||||
projectName: ctx.project.name,
|
||||
rev: doc.rev,
|
||||
path,
|
||||
folderId: ctx.folder._id,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('should flush each file to the TPDS', function () {
|
||||
for (const [path, file] of Object.entries(this.files)) {
|
||||
expect(
|
||||
this.TpdsUpdateSender.promises.addFile
|
||||
).to.have.been.calledWith({
|
||||
projectId: this.project._id,
|
||||
historyId: this.project.overleaf.history.id,
|
||||
fileId: file._id,
|
||||
hash: file.hash,
|
||||
projectName: this.project.name,
|
||||
rev: file.rev,
|
||||
path,
|
||||
folderId: this.folder._id,
|
||||
})
|
||||
it('should flush each file to the TPDS', function (ctx) {
|
||||
for (const [path, file] of Object.entries(ctx.files)) {
|
||||
expect(ctx.TpdsUpdateSender.promises.addFile).to.have.been.calledWith(
|
||||
{
|
||||
projectId: ctx.project._id,
|
||||
historyId: ctx.project.overleaf.history.id,
|
||||
fileId: file._id,
|
||||
hash: file.hash,
|
||||
projectName: ctx.project.name,
|
||||
rev: file.rev,
|
||||
path,
|
||||
folderId: ctx.folder._id,
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a TPDS flush is pending', function () {
|
||||
beforeEach(async function () {
|
||||
this.project.deferredTpdsFlushCounter = 2
|
||||
this.ProjectMock.expects('updateOne')
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.project.deferredTpdsFlushCounter = 2
|
||||
ctx.ProjectMock.expects('updateOne')
|
||||
.withArgs(
|
||||
{
|
||||
_id: this.project._id,
|
||||
_id: ctx.project._id,
|
||||
deferredTpdsFlushCounter: { $lte: 2 },
|
||||
},
|
||||
{ $set: { deferredTpdsFlushCounter: 0 } }
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves()
|
||||
await this.TpdsProjectFlusher.promises.flushProjectToTpds(
|
||||
this.project._id
|
||||
await ctx.TpdsProjectFlusher.promises.flushProjectToTpds(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
|
||||
it('resets the deferred flush counter', function () {
|
||||
this.ProjectMock.verify()
|
||||
it('resets the deferred flush counter', function (ctx) {
|
||||
ctx.ProjectMock.verify()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deferProjectFlushToTpds', function () {
|
||||
beforeEach(async function () {
|
||||
this.ProjectMock.expects('updateOne')
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectMock.expects('updateOne')
|
||||
.withArgs(
|
||||
{
|
||||
_id: this.project._id,
|
||||
_id: ctx.project._id,
|
||||
},
|
||||
{ $inc: { deferredTpdsFlushCounter: 1 } }
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves()
|
||||
await this.TpdsProjectFlusher.promises.deferProjectFlushToTpds(
|
||||
this.project._id
|
||||
await ctx.TpdsProjectFlusher.promises.deferProjectFlushToTpds(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
|
||||
it('increments the deferred flush counter', function () {
|
||||
this.ProjectMock.verify()
|
||||
it('increments the deferred flush counter', function (ctx) {
|
||||
ctx.ProjectMock.verify()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -178,24 +196,24 @@ describe('TpdsProjectFlusher', function () {
|
||||
let cases = [0, undefined]
|
||||
cases.forEach(counterValue => {
|
||||
describe(`when the deferred flush counter is ${counterValue}`, function () {
|
||||
beforeEach(async function () {
|
||||
this.project.deferredTpdsFlushCounter = counterValue
|
||||
await this.TpdsProjectFlusher.promises.flushProjectToTpdsIfNeeded(
|
||||
this.project._id
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.project.deferredTpdsFlushCounter = counterValue
|
||||
await ctx.TpdsProjectFlusher.promises.flushProjectToTpdsIfNeeded(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
|
||||
it("doesn't flush the project from the doc updater", function () {
|
||||
expect(this.DocumentUpdaterHandler.promises.flushProjectToMongo).not
|
||||
.to.have.been.called
|
||||
it("doesn't flush the project from the doc updater", function (ctx) {
|
||||
expect(ctx.DocumentUpdaterHandler.promises.flushProjectToMongo).not.to
|
||||
.have.been.called
|
||||
})
|
||||
|
||||
it("doesn't flush any doc", function () {
|
||||
expect(this.TpdsUpdateSender.promises.addDoc).not.to.have.been.called
|
||||
it("doesn't flush any doc", function (ctx) {
|
||||
expect(ctx.TpdsUpdateSender.promises.addDoc).not.to.have.been.called
|
||||
})
|
||||
|
||||
it("doesn't flush any file", function () {
|
||||
expect(this.TpdsUpdateSender.promises.addFile).not.to.have.been.called
|
||||
it("doesn't flush any file", function (ctx) {
|
||||
expect(ctx.TpdsUpdateSender.promises.addFile).not.to.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -203,63 +221,63 @@ describe('TpdsProjectFlusher', function () {
|
||||
cases = [1, 2]
|
||||
cases.forEach(counterValue => {
|
||||
describe(`when the deferred flush counter is ${counterValue}`, function () {
|
||||
beforeEach(async function () {
|
||||
this.project.deferredTpdsFlushCounter = counterValue
|
||||
this.ProjectMock.expects('updateOne')
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.project.deferredTpdsFlushCounter = counterValue
|
||||
ctx.ProjectMock.expects('updateOne')
|
||||
.withArgs(
|
||||
{
|
||||
_id: this.project._id,
|
||||
_id: ctx.project._id,
|
||||
deferredTpdsFlushCounter: { $lte: counterValue },
|
||||
},
|
||||
{ $set: { deferredTpdsFlushCounter: 0 } }
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves()
|
||||
await this.TpdsProjectFlusher.promises.flushProjectToTpdsIfNeeded(
|
||||
this.project._id
|
||||
await ctx.TpdsProjectFlusher.promises.flushProjectToTpdsIfNeeded(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
|
||||
it('flushes the project from the doc updater', function () {
|
||||
it('flushes the project from the doc updater', function (ctx) {
|
||||
expect(
|
||||
this.DocumentUpdaterHandler.promises.flushProjectToMongo
|
||||
).to.have.been.calledWith(this.project._id)
|
||||
ctx.DocumentUpdaterHandler.promises.flushProjectToMongo
|
||||
).to.have.been.calledWith(ctx.project._id)
|
||||
})
|
||||
|
||||
it('flushes each doc to the TPDS', function () {
|
||||
for (const [path, doc] of Object.entries(this.docs)) {
|
||||
it('flushes each doc to the TPDS', function (ctx) {
|
||||
for (const [path, doc] of Object.entries(ctx.docs)) {
|
||||
expect(
|
||||
this.TpdsUpdateSender.promises.addDoc
|
||||
ctx.TpdsUpdateSender.promises.addDoc
|
||||
).to.have.been.calledWith({
|
||||
projectId: this.project._id,
|
||||
projectId: ctx.project._id,
|
||||
docId: doc._id,
|
||||
projectName: this.project.name,
|
||||
projectName: ctx.project.name,
|
||||
rev: doc.rev,
|
||||
path,
|
||||
folderId: this.folder._id,
|
||||
folderId: ctx.folder._id,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('flushes each file to the TPDS', function () {
|
||||
for (const [path, file] of Object.entries(this.files)) {
|
||||
it('flushes each file to the TPDS', function (ctx) {
|
||||
for (const [path, file] of Object.entries(ctx.files)) {
|
||||
expect(
|
||||
this.TpdsUpdateSender.promises.addFile
|
||||
ctx.TpdsUpdateSender.promises.addFile
|
||||
).to.have.been.calledWith({
|
||||
projectId: this.project._id,
|
||||
historyId: this.project.overleaf.history.id,
|
||||
projectId: ctx.project._id,
|
||||
historyId: ctx.project.overleaf.history.id,
|
||||
fileId: file._id,
|
||||
hash: file.hash,
|
||||
projectName: this.project.name,
|
||||
projectName: ctx.project.name,
|
||||
rev: file.rev,
|
||||
path,
|
||||
folderId: this.folder._id,
|
||||
folderId: ctx.folder._id,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('resets the deferred flush counter', function () {
|
||||
this.ProjectMock.verify()
|
||||
it('resets the deferred flush counter', function (ctx) {
|
||||
ctx.ProjectMock.verify()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const path = require('path')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import path from 'path'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const modulePath = path.join(
|
||||
__dirname,
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js'
|
||||
import.meta.dirname,
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.mjs'
|
||||
)
|
||||
|
||||
const projectId = 'project_id_here'
|
||||
@@ -21,29 +22,29 @@ const filestoreUrl = 'filestore.overleaf.com'
|
||||
const projectHistoryUrl = 'http://project-history:3054'
|
||||
|
||||
describe('TpdsUpdateSender', function () {
|
||||
beforeEach(function () {
|
||||
this.fakeUser = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.fakeUser = {
|
||||
_id: '12390i',
|
||||
}
|
||||
this.memberIds = [userId, collaberatorRef, readOnlyRef]
|
||||
this.enqueueUrl = new URL(
|
||||
ctx.memberIds = [userId, collaberatorRef, readOnlyRef]
|
||||
ctx.enqueueUrl = new URL(
|
||||
'http://tpdsworker/enqueue/web_to_tpds_http_requests'
|
||||
)
|
||||
|
||||
this.CollaboratorsGetter = {
|
||||
ctx.CollaboratorsGetter = {
|
||||
promises: {
|
||||
getInvitedMemberIds: sinon.stub().resolves(this.memberIds),
|
||||
getInvitedMemberIds: sinon.stub().resolves(ctx.memberIds),
|
||||
},
|
||||
}
|
||||
this.docstoreUrl = 'docstore.overleaf.env'
|
||||
this.response = {
|
||||
ctx.docstoreUrl = 'docstore.overleaf.env'
|
||||
ctx.response = {
|
||||
ok: true,
|
||||
json: sinon.stub(),
|
||||
}
|
||||
this.FetchUtils = {
|
||||
ctx.FetchUtils = {
|
||||
fetchNothing: sinon.stub().resolves(),
|
||||
}
|
||||
this.settings = {
|
||||
ctx.settings = {
|
||||
siteUrl,
|
||||
apis: {
|
||||
thirdPartyDataStore: { url: thirdPartyDataStoreApiUrl },
|
||||
@@ -51,7 +52,7 @@ describe('TpdsUpdateSender', function () {
|
||||
url: filestoreUrl,
|
||||
},
|
||||
docstore: {
|
||||
pubUrl: this.docstoreUrl,
|
||||
pubUrl: ctx.docstoreUrl,
|
||||
},
|
||||
project_history: {
|
||||
url: projectHistoryUrl,
|
||||
@@ -62,46 +63,63 @@ describe('TpdsUpdateSender', function () {
|
||||
getUsers
|
||||
.withArgs({
|
||||
_id: {
|
||||
$in: this.memberIds,
|
||||
$in: ctx.memberIds,
|
||||
},
|
||||
'dropbox.access_token.uid': { $ne: null },
|
||||
})
|
||||
.resolves(
|
||||
this.memberIds.map(userId => {
|
||||
ctx.memberIds.map(userId => {
|
||||
return { _id: userId }
|
||||
})
|
||||
)
|
||||
this.UserGetter = {
|
||||
ctx.UserGetter = {
|
||||
promises: { getUsers },
|
||||
}
|
||||
this.TpdsUpdateSender = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'mongodb-legacy': { ObjectId },
|
||||
'@overleaf/settings': this.settings,
|
||||
'@overleaf/fetch-utils': this.FetchUtils,
|
||||
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
|
||||
'../User/UserGetter.js': this.UserGetter,
|
||||
'@overleaf/metrics': {
|
||||
inc() {},
|
||||
},
|
||||
|
||||
vi.doMock('mongodb-legacy', () => ({
|
||||
default: { ObjectId },
|
||||
}))
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: ctx.settings,
|
||||
}))
|
||||
|
||||
vi.doMock('@overleaf/fetch-utils', () => ctx.FetchUtils)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Collaborators/CollaboratorsGetter',
|
||||
() => ({
|
||||
default: ctx.CollaboratorsGetter,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter.js', () => ({
|
||||
default: ctx.UserGetter,
|
||||
}))
|
||||
|
||||
vi.doMock('@overleaf/metrics', () => ({
|
||||
default: {
|
||||
inc() {},
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
ctx.TpdsUpdateSender = (await import(modulePath)).default
|
||||
})
|
||||
|
||||
describe('enqueue', function () {
|
||||
it('should not call request if there is no tpdsworker url', async function () {
|
||||
await this.TpdsUpdateSender.promises.enqueue(null, null, null)
|
||||
this.FetchUtils.fetchNothing.should.not.have.been.called
|
||||
it('should not call request if there is no tpdsworker url', async function (ctx) {
|
||||
await ctx.TpdsUpdateSender.promises.enqueue(null, null, null)
|
||||
ctx.FetchUtils.fetchNothing.should.not.have.been.called
|
||||
})
|
||||
|
||||
it('should post the message to the tpdsworker', async function () {
|
||||
this.settings.apis.tpdsworker = { url: 'http://tpdsworker' }
|
||||
it('should post the message to the tpdsworker', async function (ctx) {
|
||||
ctx.settings.apis.tpdsworker = { url: 'http://tpdsworker' }
|
||||
const group0 = 'myproject'
|
||||
const method0 = 'somemethod0'
|
||||
const job0 = 'do something'
|
||||
await this.TpdsUpdateSender.promises.enqueue(group0, method0, job0)
|
||||
this.FetchUtils.fetchNothing.should.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
await ctx.TpdsUpdateSender.promises.enqueue(group0, method0, job0)
|
||||
ctx.FetchUtils.fetchNothing.should.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
method: 'POST',
|
||||
json: { group: group0, job: job0, method: method0 },
|
||||
@@ -111,17 +129,17 @@ describe('TpdsUpdateSender', function () {
|
||||
})
|
||||
|
||||
describe('sending updates', function () {
|
||||
beforeEach(function () {
|
||||
this.settings.apis.tpdsworker = { url: 'http://tpdsworker' }
|
||||
beforeEach(function (ctx) {
|
||||
ctx.settings.apis.tpdsworker = { url: 'http://tpdsworker' }
|
||||
})
|
||||
|
||||
it('queues a post the file with user and file id and hash', async function () {
|
||||
it('queues a post the file with user and file id and hash', async function (ctx) {
|
||||
const fileId = '4545345'
|
||||
const hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
const historyId = 91525
|
||||
const path = '/some/path/here.jpg'
|
||||
|
||||
await this.TpdsUpdateSender.promises.addFile({
|
||||
await ctx.TpdsUpdateSender.promises.addFile({
|
||||
projectId,
|
||||
historyId,
|
||||
fileId,
|
||||
@@ -130,8 +148,8 @@ describe('TpdsUpdateSender', function () {
|
||||
projectName,
|
||||
})
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: userId,
|
||||
@@ -148,8 +166,8 @@ describe('TpdsUpdateSender', function () {
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: collaberatorRef,
|
||||
@@ -157,8 +175,8 @@ describe('TpdsUpdateSender', function () {
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: readOnlyRef,
|
||||
@@ -168,12 +186,12 @@ describe('TpdsUpdateSender', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('post doc with stream origin of docstore', async function () {
|
||||
it('post doc with stream origin of docstore', async function (ctx) {
|
||||
const docId = '4545345'
|
||||
const path = '/some/path/here.tex'
|
||||
const lines = ['line1', 'line2', 'line3']
|
||||
|
||||
await this.TpdsUpdateSender.promises.addDoc({
|
||||
await ctx.TpdsUpdateSender.promises.addDoc({
|
||||
projectId,
|
||||
docId,
|
||||
path,
|
||||
@@ -181,8 +199,8 @@ describe('TpdsUpdateSender', function () {
|
||||
projectName,
|
||||
})
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: userId,
|
||||
@@ -192,15 +210,15 @@ describe('TpdsUpdateSender', function () {
|
||||
uri: `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
|
||||
projectName
|
||||
)}${encodeURIComponent(path)}`,
|
||||
streamOrigin: `${this.docstoreUrl}/project/${projectId}/doc/${docId}/raw`,
|
||||
streamOrigin: `${ctx.docstoreUrl}/project/${projectId}/doc/${docId}/raw`,
|
||||
headers: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: collaberatorRef,
|
||||
@@ -211,8 +229,8 @@ describe('TpdsUpdateSender', function () {
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: readOnlyRef,
|
||||
@@ -224,19 +242,19 @@ describe('TpdsUpdateSender', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('deleting entity', async function () {
|
||||
it('deleting entity', async function (ctx) {
|
||||
const path = '/path/here/t.tex'
|
||||
const subtreeEntityIds = ['id1', 'id2']
|
||||
|
||||
await this.TpdsUpdateSender.promises.deleteEntity({
|
||||
await ctx.TpdsUpdateSender.promises.deleteEntity({
|
||||
projectId,
|
||||
path,
|
||||
projectName,
|
||||
subtreeEntityIds,
|
||||
})
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: userId,
|
||||
@@ -253,8 +271,8 @@ describe('TpdsUpdateSender', function () {
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: collaberatorRef,
|
||||
@@ -265,8 +283,8 @@ describe('TpdsUpdateSender', function () {
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: readOnlyRef,
|
||||
@@ -278,19 +296,19 @@ describe('TpdsUpdateSender', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('moving entity', async function () {
|
||||
it('moving entity', async function (ctx) {
|
||||
const startPath = 'staring/here/file.tex'
|
||||
const endPath = 'ending/here/file.tex'
|
||||
|
||||
await this.TpdsUpdateSender.promises.moveEntity({
|
||||
await ctx.TpdsUpdateSender.promises.moveEntity({
|
||||
projectId,
|
||||
startPath,
|
||||
endPath,
|
||||
projectName,
|
||||
})
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: userId,
|
||||
@@ -308,8 +326,8 @@ describe('TpdsUpdateSender', function () {
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: collaberatorRef,
|
||||
@@ -320,8 +338,8 @@ describe('TpdsUpdateSender', function () {
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: readOnlyRef,
|
||||
@@ -333,18 +351,18 @@ describe('TpdsUpdateSender', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should be able to rename a project using the move entity func', async function () {
|
||||
it('should be able to rename a project using the move entity func', async function (ctx) {
|
||||
const oldProjectName = '/oldProjectName/'
|
||||
const newProjectName = '/newProjectName/'
|
||||
|
||||
await this.TpdsUpdateSender.promises.moveEntity({
|
||||
await ctx.TpdsUpdateSender.promises.moveEntity({
|
||||
projectId,
|
||||
projectName: oldProjectName,
|
||||
newProjectName,
|
||||
})
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: userId,
|
||||
@@ -362,8 +380,8 @@ describe('TpdsUpdateSender', function () {
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: collaberatorRef,
|
||||
@@ -374,8 +392,8 @@ describe('TpdsUpdateSender', function () {
|
||||
}
|
||||
)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: readOnlyRef,
|
||||
@@ -387,11 +405,11 @@ describe('TpdsUpdateSender', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('pollDropboxForUser', async function () {
|
||||
await this.TpdsUpdateSender.promises.pollDropboxForUser(userId)
|
||||
it('pollDropboxForUser', async function (ctx) {
|
||||
await ctx.TpdsUpdateSender.promises.pollDropboxForUser(userId)
|
||||
|
||||
expect(this.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
this.enqueueUrl,
|
||||
expect(ctx.FetchUtils.fetchNothing).to.have.been.calledWithMatch(
|
||||
ctx.enqueueUrl,
|
||||
{
|
||||
json: {
|
||||
group: userId,
|
||||
@@ -410,24 +428,24 @@ describe('TpdsUpdateSender', function () {
|
||||
})
|
||||
|
||||
describe('user not linked to dropbox', function () {
|
||||
beforeEach(function () {
|
||||
this.UserGetter.promises.getUsers
|
||||
beforeEach(function (ctx) {
|
||||
ctx.UserGetter.promises.getUsers
|
||||
.withArgs({
|
||||
_id: {
|
||||
$in: this.memberIds,
|
||||
$in: ctx.memberIds,
|
||||
},
|
||||
'dropbox.access_token.uid': { $ne: null },
|
||||
})
|
||||
.resolves([])
|
||||
})
|
||||
|
||||
it('does not make request to tpds', async function () {
|
||||
it('does not make request to tpds', async function (ctx) {
|
||||
const fileId = '4545345'
|
||||
const hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
const historyId = 91525
|
||||
const path = '/some/path/here.jpg'
|
||||
|
||||
await this.TpdsUpdateSender.promises.addFile({
|
||||
await ctx.TpdsUpdateSender.promises.addFile({
|
||||
projectId,
|
||||
historyId,
|
||||
hash,
|
||||
@@ -435,7 +453,7 @@ describe('TpdsUpdateSender', function () {
|
||||
path,
|
||||
projectName,
|
||||
})
|
||||
this.FetchUtils.fetchNothing.should.not.have.been.called
|
||||
ctx.FetchUtils.fetchNothing.should.not.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -236,12 +236,13 @@ describe('TokenAccessController', function () {
|
||||
}),
|
||||
}))
|
||||
|
||||
ctx.AdminAuthorizationHelper = {
|
||||
canRedirectToAdminDomain: sinon.stub(),
|
||||
}
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Helpers/AdminAuthorizationHelper',
|
||||
() =>
|
||||
(ctx.AdminAuthorizationHelper = {
|
||||
canRedirectToAdminDomain: sinon.stub(),
|
||||
})
|
||||
() => ({ default: ctx.AdminAuthorizationHelper })
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
@@ -764,17 +765,12 @@ describe('TokenAccessController', function () {
|
||||
.stub()
|
||||
.resolves([{ email: 'test@not-overleaf.com' }])
|
||||
|
||||
await new Promise(resolve => {
|
||||
ctx.res.callback = () => {
|
||||
expect(ctx.res.json).to.have.been.calledWith({
|
||||
redirect: `${ctx.Settings.adminUrl}/#prefix`,
|
||||
})
|
||||
resolve()
|
||||
}
|
||||
ctx.TokenAccessController.grantTokenAccessReadAndWrite(
|
||||
ctx.req,
|
||||
ctx.res
|
||||
)
|
||||
await ctx.TokenAccessController.grantTokenAccessReadAndWrite(
|
||||
ctx.req,
|
||||
ctx.res
|
||||
)
|
||||
expect(ctx.res.json).to.have.been.calledWith({
|
||||
redirect: `${ctx.Settings.adminUrl}/#prefix`,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,48 +1,53 @@
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const mockFs = require('mock-fs')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
const Settings = require('@overleaf/settings')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import mockFs from 'mock-fs'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import Settings from '@overleaf/settings'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/Uploads/FileSystemImportManager.js'
|
||||
'../../../../app/src/Features/Uploads/FileSystemImportManager.mjs'
|
||||
|
||||
describe('FileSystemImportManager', function () {
|
||||
beforeEach(function () {
|
||||
this.projectId = new ObjectId()
|
||||
this.folderId = new ObjectId()
|
||||
this.newFolderId = new ObjectId()
|
||||
this.userId = new ObjectId()
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.projectId = new ObjectId()
|
||||
ctx.folderId = new ObjectId()
|
||||
ctx.newFolderId = new ObjectId()
|
||||
ctx.userId = new ObjectId()
|
||||
|
||||
this.EditorController = {
|
||||
ctx.EditorController = {
|
||||
promises: {
|
||||
addDoc: sinon.stub().resolves(),
|
||||
addFile: sinon.stub().resolves(),
|
||||
upsertDoc: sinon.stub().resolves(),
|
||||
upsertFile: sinon.stub().resolves(),
|
||||
addFolder: sinon.stub().resolves({ _id: this.newFolderId }),
|
||||
addFolder: sinon.stub().resolves({ _id: ctx.newFolderId }),
|
||||
},
|
||||
}
|
||||
this.FileSystemImportManager = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'@overleaf/settings': {
|
||||
textExtensions: ['tex', 'txt'],
|
||||
editableFilenames: [
|
||||
'latexmkrc',
|
||||
'.latexmkrc',
|
||||
'makefile',
|
||||
'gnumakefile',
|
||||
],
|
||||
fileIgnorePattern: Settings.fileIgnorePattern, // use the real pattern from the default settings
|
||||
},
|
||||
'../Editor/EditorController': this.EditorController,
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: {
|
||||
textExtensions: ['tex', 'txt'],
|
||||
editableFilenames: [
|
||||
'latexmkrc',
|
||||
'.latexmkrc',
|
||||
'makefile',
|
||||
'gnumakefile',
|
||||
],
|
||||
fileIgnorePattern: Settings.fileIgnorePattern, // use the real pattern from the default settings
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Editor/EditorController', () => ({
|
||||
default: ctx.EditorController,
|
||||
}))
|
||||
|
||||
ctx.FileSystemImportManager = (await import(MODULE_PATH)).default
|
||||
})
|
||||
|
||||
describe('importDir', function () {
|
||||
beforeEach(async function () {
|
||||
beforeEach(async function (ctx) {
|
||||
mockFs({
|
||||
'import-test': {
|
||||
'main.tex': 'My thesis',
|
||||
@@ -64,87 +69,87 @@ describe('FileSystemImportManager', function () {
|
||||
},
|
||||
symlink: mockFs.symlink({ path: 'import-test' }),
|
||||
})
|
||||
this.entries =
|
||||
await this.FileSystemImportManager.promises.importDir('import-test')
|
||||
this.projectPaths = this.entries.map(x => x.projectPath)
|
||||
ctx.entries =
|
||||
await ctx.FileSystemImportManager.promises.importDir('import-test')
|
||||
ctx.projectPaths = ctx.entries.map(x => x.projectPath)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
mockFs.restore()
|
||||
})
|
||||
|
||||
it('should import regular docs', function () {
|
||||
expect(this.entries).to.deep.include({
|
||||
it('should import regular docs', function (ctx) {
|
||||
expect(ctx.entries).to.deep.include({
|
||||
type: 'doc',
|
||||
projectPath: '/main.tex',
|
||||
lines: ['My thesis'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should skip symlinks inside the import folder', function () {
|
||||
expect(this.projectPaths).not.to.include('/link-to-main.tex')
|
||||
it('should skip symlinks inside the import folder', function (ctx) {
|
||||
expect(ctx.projectPaths).not.to.include('/link-to-main.tex')
|
||||
})
|
||||
|
||||
it('should skip ignored files', function () {
|
||||
expect(this.projectPaths).not.to.include('/.DS_Store')
|
||||
it('should skip ignored files', function (ctx) {
|
||||
expect(ctx.projectPaths).not.to.include('/.DS_Store')
|
||||
})
|
||||
|
||||
it('should import binary files', function () {
|
||||
expect(this.entries).to.deep.include({
|
||||
it('should import binary files', function (ctx) {
|
||||
expect(ctx.entries).to.deep.include({
|
||||
type: 'file',
|
||||
projectPath: '/images/cat.jpg',
|
||||
fsPath: 'import-test/images/cat.jpg',
|
||||
})
|
||||
})
|
||||
|
||||
it('should deal with Mac/Windows/Unix line endings', function () {
|
||||
expect(this.entries).to.deep.include({
|
||||
it('should deal with Mac/Windows/Unix line endings', function (ctx) {
|
||||
expect(ctx.entries).to.deep.include({
|
||||
type: 'doc',
|
||||
projectPath: '/line-endings/unix.txt',
|
||||
lines: ['one', 'two', 'three'],
|
||||
})
|
||||
expect(this.entries).to.deep.include({
|
||||
expect(ctx.entries).to.deep.include({
|
||||
type: 'doc',
|
||||
projectPath: '/line-endings/mac.txt',
|
||||
lines: ['uno', 'dos', 'tres'],
|
||||
})
|
||||
expect(this.entries).to.deep.include({
|
||||
expect(ctx.entries).to.deep.include({
|
||||
type: 'doc',
|
||||
projectPath: '/line-endings/windows.txt',
|
||||
lines: ['ein', 'zwei', 'drei'],
|
||||
})
|
||||
expect(this.entries).to.deep.include({
|
||||
expect(ctx.entries).to.deep.include({
|
||||
type: 'doc',
|
||||
projectPath: '/line-endings/mixed.txt',
|
||||
lines: ['uno', 'due', 'tre', 'quattro'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should import documents with latin1 encoding', function () {
|
||||
expect(this.entries).to.deep.include({
|
||||
it('should import documents with latin1 encoding', function (ctx) {
|
||||
expect(ctx.entries).to.deep.include({
|
||||
type: 'doc',
|
||||
projectPath: '/encodings/latin1.txt',
|
||||
lines: ['tétanisant!'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should import documents with utf16-le encoding', function () {
|
||||
expect(this.entries).to.deep.include({
|
||||
it('should import documents with utf16-le encoding', function (ctx) {
|
||||
expect(ctx.entries).to.deep.include({
|
||||
type: 'doc',
|
||||
projectPath: '/encodings/utf16le.txt',
|
||||
lines: ['\ufeffétonnant!'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should error when the root folder is a symlink', async function () {
|
||||
await expect(this.FileSystemImportManager.promises.importDir('symlink'))
|
||||
.to.be.rejected
|
||||
it('should error when the root folder is a symlink', async function (ctx) {
|
||||
await expect(ctx.FileSystemImportManager.promises.importDir('symlink')).to
|
||||
.be.rejected
|
||||
})
|
||||
})
|
||||
|
||||
describe('addEntity', function () {
|
||||
describe('with directory', function () {
|
||||
beforeEach(async function () {
|
||||
beforeEach(async function (ctx) {
|
||||
mockFs({
|
||||
path: {
|
||||
to: {
|
||||
@@ -156,10 +161,10 @@ describe('FileSystemImportManager', function () {
|
||||
},
|
||||
})
|
||||
|
||||
await this.FileSystemImportManager.promises.addEntity(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
await ctx.FileSystemImportManager.promises.addEntity(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'folder',
|
||||
'path/to/folder',
|
||||
false
|
||||
@@ -170,32 +175,32 @@ describe('FileSystemImportManager', function () {
|
||||
mockFs.restore()
|
||||
})
|
||||
|
||||
it('should add a folder to the project', function () {
|
||||
this.EditorController.promises.addFolder.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
it('should add a folder to the project', function (ctx) {
|
||||
ctx.EditorController.promises.addFolder.should.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'folder',
|
||||
'upload'
|
||||
)
|
||||
})
|
||||
|
||||
it("should add the folder's contents", function () {
|
||||
this.EditorController.promises.addDoc.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.newFolderId,
|
||||
it("should add the folder's contents", function (ctx) {
|
||||
ctx.EditorController.promises.addDoc.should.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.newFolderId,
|
||||
'doc.tex',
|
||||
['one', 'two', 'three'],
|
||||
'upload',
|
||||
this.userId
|
||||
ctx.userId
|
||||
)
|
||||
this.EditorController.promises.addFile.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.newFolderId,
|
||||
ctx.EditorController.promises.addFile.should.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.newFolderId,
|
||||
'image.jpg',
|
||||
'path/to/folder/image.jpg',
|
||||
null,
|
||||
'upload',
|
||||
this.userId
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -210,51 +215,51 @@ describe('FileSystemImportManager', function () {
|
||||
})
|
||||
|
||||
describe('with replace set to false', function () {
|
||||
beforeEach(async function () {
|
||||
await this.FileSystemImportManager.promises.addEntity(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.FileSystemImportManager.promises.addEntity(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'image.jpg',
|
||||
'uploaded-file',
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should add the file', function () {
|
||||
this.EditorController.promises.addFile.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
it('should add the file', function (ctx) {
|
||||
ctx.EditorController.promises.addFile.should.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'image.jpg',
|
||||
'uploaded-file',
|
||||
null,
|
||||
'upload',
|
||||
this.userId
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with replace set to true', function () {
|
||||
beforeEach(async function () {
|
||||
await this.FileSystemImportManager.promises.addEntity(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.FileSystemImportManager.promises.addEntity(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'image.jpg',
|
||||
'uploaded-file',
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should add the file', function () {
|
||||
this.EditorController.promises.upsertFile.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
it('should add the file', function (ctx) {
|
||||
ctx.EditorController.promises.upsertFile.should.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'image.jpg',
|
||||
'uploaded-file',
|
||||
null,
|
||||
'upload',
|
||||
this.userId
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -279,49 +284,49 @@ describe('FileSystemImportManager', function () {
|
||||
})
|
||||
|
||||
describe('with replace set to false', function () {
|
||||
beforeEach(async function () {
|
||||
await this.FileSystemImportManager.promises.addEntity(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.FileSystemImportManager.promises.addEntity(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'doc.tex',
|
||||
'path/to/uploaded-file',
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should insert the doc', function () {
|
||||
this.EditorController.promises.addDoc.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
it('should insert the doc', function (ctx) {
|
||||
ctx.EditorController.promises.addDoc.should.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'doc.tex',
|
||||
['one', 'two', 'three'],
|
||||
'upload',
|
||||
this.userId
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with replace set to true', function () {
|
||||
beforeEach(async function () {
|
||||
await this.FileSystemImportManager.promises.addEntity(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.FileSystemImportManager.promises.addEntity(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'doc.tex',
|
||||
'path/to/uploaded-file',
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should upsert the doc', function () {
|
||||
this.EditorController.promises.upsertDoc.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
it('should upsert the doc', function (ctx) {
|
||||
ctx.EditorController.promises.upsertDoc.should.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'doc.tex',
|
||||
['one', 'two', 'three'],
|
||||
'upload',
|
||||
this.userId
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -339,20 +344,20 @@ describe('FileSystemImportManager', function () {
|
||||
mockFs.restore()
|
||||
})
|
||||
|
||||
it('should stop with an error', async function () {
|
||||
it('should stop with an error', async function (ctx) {
|
||||
await expect(
|
||||
this.FileSystemImportManager.promises.addEntity(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.folderId,
|
||||
ctx.FileSystemImportManager.promises.addEntity(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.folderId,
|
||||
'main.tex',
|
||||
'path/to/symlink',
|
||||
false
|
||||
)
|
||||
).to.be.rejectedWith('path is symlink')
|
||||
this.EditorController.promises.addFolder.should.not.have.been.called
|
||||
this.EditorController.promises.addDoc.should.not.have.been.called
|
||||
this.EditorController.promises.addFile.should.not.have.been.called
|
||||
ctx.EditorController.promises.addFolder.should.not.have.been.called
|
||||
ctx.EditorController.promises.addDoc.should.not.have.been.called
|
||||
ctx.EditorController.promises.addFile.should.not.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,176 +1,238 @@
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const timekeeper = require('timekeeper')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import timekeeper from 'timekeeper'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/Uploads/ProjectUploadManager.js'
|
||||
'../../../../app/src/Features/Uploads/ProjectUploadManager.mjs'
|
||||
|
||||
describe('ProjectUploadManager', function () {
|
||||
beforeEach(function () {
|
||||
this.now = Date.now()
|
||||
timekeeper.freeze(this.now)
|
||||
this.rootFolderId = new ObjectId()
|
||||
this.ownerId = new ObjectId()
|
||||
this.zipPath = '/path/to/zip/file-name.zip'
|
||||
this.extractedZipPath = `/path/to/zip/file-name-${this.now}`
|
||||
this.mainContent = 'Contents of main.tex'
|
||||
this.projectName = 'My project*'
|
||||
this.fixedProjectName = 'My project'
|
||||
this.uniqueProjectName = 'My project (1)'
|
||||
this.project = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.now = Date.now()
|
||||
timekeeper.freeze(ctx.now)
|
||||
ctx.rootFolderId = new ObjectId()
|
||||
ctx.ownerId = new ObjectId()
|
||||
ctx.zipPath = '/path/to/zip/file-name.zip'
|
||||
ctx.extractedZipPath = `/path/to/zip/file-name-${ctx.now}`
|
||||
ctx.mainContent = 'Contents of main.tex'
|
||||
ctx.projectName = 'My project*'
|
||||
ctx.fixedProjectName = 'My project'
|
||||
ctx.uniqueProjectName = 'My project (1)'
|
||||
ctx.project = {
|
||||
_id: new ObjectId(),
|
||||
rootFolder: [{ _id: this.rootFolderId }],
|
||||
rootFolder: [{ _id: ctx.rootFolderId }],
|
||||
overleaf: { history: { id: 12345 } },
|
||||
}
|
||||
this.doc = {
|
||||
ctx.doc = {
|
||||
_id: new ObjectId(),
|
||||
name: 'main.tex',
|
||||
}
|
||||
this.docFsPath = '/path/to/doc'
|
||||
this.docLines = ['My thesis', 'by A. U. Thor']
|
||||
this.file = {
|
||||
ctx.docFsPath = '/path/to/doc'
|
||||
ctx.docLines = ['My thesis', 'by A. U. Thor']
|
||||
ctx.file = {
|
||||
_id: new ObjectId(),
|
||||
name: 'image.png',
|
||||
}
|
||||
this.fileFsPath = '/path/to/file'
|
||||
ctx.fileFsPath = '/path/to/file'
|
||||
|
||||
this.topLevelDestination = '/path/to/zip/file-extracted/nested'
|
||||
this.newProjectVersion = 123
|
||||
this.importEntries = [
|
||||
ctx.topLevelDestination = '/path/to/zip/file-extracted/nested'
|
||||
ctx.newProjectVersion = 123
|
||||
ctx.importEntries = [
|
||||
{
|
||||
type: 'doc',
|
||||
projectPath: '/main.tex',
|
||||
lines: this.docLines,
|
||||
lines: ctx.docLines,
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
projectPath: `/${this.file.name}`,
|
||||
fsPath: this.fileFsPath,
|
||||
projectPath: `/${ctx.file.name}`,
|
||||
fsPath: ctx.fileFsPath,
|
||||
},
|
||||
]
|
||||
this.docEntries = [
|
||||
ctx.docEntries = [
|
||||
{
|
||||
doc: this.doc,
|
||||
path: `/${this.doc.name}`,
|
||||
docLines: this.docLines.join('\n'),
|
||||
doc: ctx.doc,
|
||||
path: `/${ctx.doc.name}`,
|
||||
docLines: ctx.docLines.join('\n'),
|
||||
},
|
||||
]
|
||||
this.fileEntries = [
|
||||
ctx.fileEntries = [
|
||||
{
|
||||
file: this.file,
|
||||
path: `/${this.file.name}`,
|
||||
file: ctx.file,
|
||||
path: `/${ctx.file.name}`,
|
||||
createdBlob: true,
|
||||
},
|
||||
]
|
||||
|
||||
this.fs = {
|
||||
ctx.fs = {
|
||||
promises: {
|
||||
rm: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.ArchiveManager = {
|
||||
ctx.ArchiveManager = {
|
||||
promises: {
|
||||
extractZipArchive: sinon.stub().resolves(),
|
||||
findTopLevelDirectory: sinon
|
||||
.stub()
|
||||
.withArgs(this.extractedZipPath)
|
||||
.resolves(this.topLevelDestination),
|
||||
.withArgs(ctx.extractedZipPath)
|
||||
.resolves(ctx.topLevelDestination),
|
||||
},
|
||||
}
|
||||
this.Doc = sinon.stub().returns(this.doc)
|
||||
this.DocstoreManager = {
|
||||
ctx.Doc = sinon.stub().returns(ctx.doc)
|
||||
ctx.DocstoreManager = {
|
||||
promises: {
|
||||
updateDoc: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.DocumentHelper = {
|
||||
ctx.DocumentHelper = {
|
||||
getTitleFromTexContent: sinon
|
||||
.stub()
|
||||
.withArgs(this.mainContent)
|
||||
.returns(this.projectName),
|
||||
.withArgs(ctx.mainContent)
|
||||
.returns(ctx.projectName),
|
||||
}
|
||||
this.DocumentUpdaterHandler = {
|
||||
ctx.DocumentUpdaterHandler = {
|
||||
promises: {
|
||||
updateProjectStructure: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.FileStoreHandler = {
|
||||
ctx.FileStoreHandler = {
|
||||
promises: {
|
||||
uploadFileFromDiskWithHistoryId: sinon.stub().resolves({
|
||||
fileRef: this.file,
|
||||
fileRef: ctx.file,
|
||||
createdBlob: true,
|
||||
}),
|
||||
},
|
||||
}
|
||||
this.FileSystemImportManager = {
|
||||
ctx.FileSystemImportManager = {
|
||||
promises: {
|
||||
importDir: sinon
|
||||
.stub()
|
||||
.withArgs(this.topLevelDestination)
|
||||
.resolves(this.importEntries),
|
||||
.withArgs(ctx.topLevelDestination)
|
||||
.resolves(ctx.importEntries),
|
||||
},
|
||||
}
|
||||
this.ProjectCreationHandler = {
|
||||
ctx.ProjectCreationHandler = {
|
||||
promises: {
|
||||
createBlankProject: sinon.stub().resolves(this.project),
|
||||
createBlankProject: sinon.stub().resolves(ctx.project),
|
||||
},
|
||||
}
|
||||
this.ProjectEntityMongoUpdateHandler = {
|
||||
ctx.ProjectEntityMongoUpdateHandler = {
|
||||
promises: {
|
||||
createNewFolderStructure: sinon.stub().resolves(this.newProjectVersion),
|
||||
createNewFolderStructure: sinon.stub().resolves(ctx.newProjectVersion),
|
||||
},
|
||||
}
|
||||
this.ProjectRootDocManager = {
|
||||
ctx.ProjectRootDocManager = {
|
||||
promises: {
|
||||
setRootDocAutomatically: sinon.stub().resolves(),
|
||||
findRootDocFileFromDirectory: sinon
|
||||
.stub()
|
||||
.resolves({ path: 'main.tex', content: this.mainContent }),
|
||||
.resolves({ path: 'main.tex', content: ctx.mainContent }),
|
||||
setRootDocFromName: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.ProjectDetailsHandler = {
|
||||
ctx.ProjectDetailsHandler = {
|
||||
fixProjectName: sinon
|
||||
.stub()
|
||||
.withArgs(this.projectName)
|
||||
.returns(this.fixedProjectName),
|
||||
.withArgs(ctx.projectName)
|
||||
.returns(ctx.fixedProjectName),
|
||||
promises: {
|
||||
generateUniqueName: sinon.stub().resolves(this.uniqueProjectName),
|
||||
generateUniqueName: sinon.stub().resolves(ctx.uniqueProjectName),
|
||||
},
|
||||
}
|
||||
this.ProjectDeleter = {
|
||||
ctx.ProjectDeleter = {
|
||||
promises: {
|
||||
deleteProject: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.TpdsProjectFlusher = {
|
||||
ctx.TpdsProjectFlusher = {
|
||||
promises: {
|
||||
flushProjectToTpds: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.ProjectUploadManager = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
fs: this.fs,
|
||||
'./ArchiveManager': this.ArchiveManager,
|
||||
'../../models/Doc': { Doc: this.Doc },
|
||||
'../Docstore/DocstoreManager': this.DocstoreManager,
|
||||
'../Documents/DocumentHelper': this.DocumentHelper,
|
||||
'../DocumentUpdater/DocumentUpdaterHandler':
|
||||
this.DocumentUpdaterHandler,
|
||||
'../FileStore/FileStoreHandler': this.FileStoreHandler,
|
||||
'./FileSystemImportManager': this.FileSystemImportManager,
|
||||
'../Project/ProjectCreationHandler': this.ProjectCreationHandler,
|
||||
'../Project/ProjectEntityMongoUpdateHandler':
|
||||
this.ProjectEntityMongoUpdateHandler,
|
||||
'../Project/ProjectRootDocManager': this.ProjectRootDocManager,
|
||||
'../Project/ProjectDetailsHandler': this.ProjectDetailsHandler,
|
||||
'../Project/ProjectDeleter': this.ProjectDeleter,
|
||||
'../ThirdPartyDataStore/TpdsProjectFlusher': this.TpdsProjectFlusher,
|
||||
},
|
||||
})
|
||||
vi.doMock('fs', () => ({
|
||||
default: ctx.fs,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Uploads/ArchiveManager', () => ({
|
||||
default: ctx.ArchiveManager,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/models/Doc', () => ({
|
||||
Doc: ctx.Doc,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Docstore/DocstoreManager', () => ({
|
||||
default: ctx.DocstoreManager,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Documents/DocumentHelper', () => ({
|
||||
default: ctx.DocumentHelper,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler',
|
||||
() => ({
|
||||
default: ctx.DocumentUpdaterHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/FileStore/FileStoreHandler',
|
||||
() => ({
|
||||
default: ctx.FileStoreHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Uploads/FileSystemImportManager',
|
||||
() => ({
|
||||
default: ctx.FileSystemImportManager,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectCreationHandler',
|
||||
() => ({
|
||||
default: ctx.ProjectCreationHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectEntityMongoUpdateHandler',
|
||||
() => ({
|
||||
default: ctx.ProjectEntityMongoUpdateHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectRootDocManager',
|
||||
() => ({
|
||||
default: ctx.ProjectRootDocManager,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectDetailsHandler',
|
||||
() => ({
|
||||
default: ctx.ProjectDetailsHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectDeleter', () => ({
|
||||
default: ctx.ProjectDeleter,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/TpdsProjectFlusher',
|
||||
() => ({
|
||||
default: ctx.TpdsProjectFlusher,
|
||||
})
|
||||
)
|
||||
|
||||
ctx.ProjectUploadManager = (await import(MODULE_PATH)).default
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
@@ -179,65 +241,65 @@ describe('ProjectUploadManager', function () {
|
||||
|
||||
describe('createProjectFromZipArchive', function () {
|
||||
describe('when the title can be read from the root document', function () {
|
||||
beforeEach(async function () {
|
||||
await this.ProjectUploadManager.promises.createProjectFromZipArchive(
|
||||
this.ownerId,
|
||||
this.projectName,
|
||||
this.zipPath
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.ProjectUploadManager.promises.createProjectFromZipArchive(
|
||||
ctx.ownerId,
|
||||
ctx.projectName,
|
||||
ctx.zipPath
|
||||
)
|
||||
})
|
||||
|
||||
it('should extract the archive', function () {
|
||||
this.ArchiveManager.promises.extractZipArchive.should.have.been.calledWith(
|
||||
this.zipPath,
|
||||
this.extractedZipPath
|
||||
it('should extract the archive', function (ctx) {
|
||||
ctx.ArchiveManager.promises.extractZipArchive.should.have.been.calledWith(
|
||||
ctx.zipPath,
|
||||
ctx.extractedZipPath
|
||||
)
|
||||
})
|
||||
|
||||
it('should create a project', function () {
|
||||
this.ProjectCreationHandler.promises.createBlankProject.should.have.been.calledWith(
|
||||
this.ownerId,
|
||||
this.uniqueProjectName
|
||||
it('should create a project', function (ctx) {
|
||||
ctx.ProjectCreationHandler.promises.createBlankProject.should.have.been.calledWith(
|
||||
ctx.ownerId,
|
||||
ctx.uniqueProjectName
|
||||
)
|
||||
})
|
||||
|
||||
it('should initialize the file tree', function () {
|
||||
this.ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure.should.have.been.calledWith(
|
||||
this.project._id,
|
||||
this.docEntries,
|
||||
this.fileEntries
|
||||
it('should initialize the file tree', function (ctx) {
|
||||
ctx.ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure.should.have.been.calledWith(
|
||||
ctx.project._id,
|
||||
ctx.docEntries,
|
||||
ctx.fileEntries
|
||||
)
|
||||
})
|
||||
|
||||
it('should notify document updater', function () {
|
||||
this.DocumentUpdaterHandler.promises.updateProjectStructure.should.have.been.calledWith(
|
||||
this.project._id,
|
||||
this.project.overleaf.history.id,
|
||||
this.ownerId,
|
||||
it('should notify document updater', function (ctx) {
|
||||
ctx.DocumentUpdaterHandler.promises.updateProjectStructure.should.have.been.calledWith(
|
||||
ctx.project._id,
|
||||
ctx.project.overleaf.history.id,
|
||||
ctx.ownerId,
|
||||
{
|
||||
newDocs: this.docEntries,
|
||||
newFiles: this.fileEntries,
|
||||
newProject: { version: this.newProjectVersion },
|
||||
newDocs: ctx.docEntries,
|
||||
newFiles: ctx.fileEntries,
|
||||
newProject: { version: ctx.newProjectVersion },
|
||||
},
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
it('should flush the project to TPDS', function () {
|
||||
this.TpdsProjectFlusher.promises.flushProjectToTpds.should.have.been.calledWith(
|
||||
this.project._id
|
||||
it('should flush the project to TPDS', function (ctx) {
|
||||
ctx.TpdsProjectFlusher.promises.flushProjectToTpds.should.have.been.calledWith(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
|
||||
it('should set the root document', function () {
|
||||
this.ProjectRootDocManager.promises.setRootDocFromName.should.have.been.calledWith(
|
||||
this.project._id,
|
||||
it('should set the root document', function (ctx) {
|
||||
ctx.ProjectRootDocManager.promises.setRootDocFromName.should.have.been.calledWith(
|
||||
ctx.project._id,
|
||||
'main.tex'
|
||||
)
|
||||
})
|
||||
|
||||
it('should remove the destination directory afterwards', function () {
|
||||
this.fs.promises.rm.should.have.been.calledWith(this.extractedZipPath, {
|
||||
it('should remove the destination directory afterwards', function (ctx) {
|
||||
ctx.fs.promises.rm.should.have.been.calledWith(ctx.extractedZipPath, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
})
|
||||
@@ -245,122 +307,122 @@ describe('ProjectUploadManager', function () {
|
||||
})
|
||||
|
||||
describe("when the root document can't be determined", function () {
|
||||
beforeEach(async function () {
|
||||
this.ProjectRootDocManager.promises.findRootDocFileFromDirectory.resolves(
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory.resolves(
|
||||
{}
|
||||
)
|
||||
await this.ProjectUploadManager.promises.createProjectFromZipArchive(
|
||||
this.ownerId,
|
||||
this.projectName,
|
||||
this.zipPath
|
||||
await ctx.ProjectUploadManager.promises.createProjectFromZipArchive(
|
||||
ctx.ownerId,
|
||||
ctx.projectName,
|
||||
ctx.zipPath
|
||||
)
|
||||
})
|
||||
|
||||
it('should not try to set the root doc', function () {
|
||||
this.ProjectRootDocManager.promises.setRootDocFromName.should.not.have
|
||||
it('should not try to set the root doc', function (ctx) {
|
||||
ctx.ProjectRootDocManager.promises.setRootDocFromName.should.not.have
|
||||
.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('createProjectFromZipArchiveWithName', function () {
|
||||
beforeEach(async function () {
|
||||
await this.ProjectUploadManager.promises.createProjectFromZipArchiveWithName(
|
||||
this.ownerId,
|
||||
this.projectName,
|
||||
this.zipPath
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.ProjectUploadManager.promises.createProjectFromZipArchiveWithName(
|
||||
ctx.ownerId,
|
||||
ctx.projectName,
|
||||
ctx.zipPath
|
||||
)
|
||||
})
|
||||
|
||||
it('should extract the archive', function () {
|
||||
this.ArchiveManager.promises.extractZipArchive.should.have.been.calledWith(
|
||||
this.zipPath,
|
||||
this.extractedZipPath
|
||||
it('should extract the archive', function (ctx) {
|
||||
ctx.ArchiveManager.promises.extractZipArchive.should.have.been.calledWith(
|
||||
ctx.zipPath,
|
||||
ctx.extractedZipPath
|
||||
)
|
||||
})
|
||||
|
||||
it('should create a project owned by the owner_id', function () {
|
||||
this.ProjectCreationHandler.promises.createBlankProject.should.have.been.calledWith(
|
||||
this.ownerId,
|
||||
this.uniqueProjectName
|
||||
it('should create a project owned by the owner_id', function (ctx) {
|
||||
ctx.ProjectCreationHandler.promises.createBlankProject.should.have.been.calledWith(
|
||||
ctx.ownerId,
|
||||
ctx.uniqueProjectName
|
||||
)
|
||||
})
|
||||
|
||||
it('should automatically set the root doc', function () {
|
||||
this.ProjectRootDocManager.promises.setRootDocAutomatically.should.have.been.calledWith(
|
||||
this.project._id
|
||||
it('should automatically set the root doc', function (ctx) {
|
||||
ctx.ProjectRootDocManager.promises.setRootDocAutomatically.should.have.been.calledWith(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
|
||||
it('should initialize the file tree', function () {
|
||||
this.ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure.should.have.been.calledWith(
|
||||
this.project._id,
|
||||
this.docEntries,
|
||||
this.fileEntries
|
||||
it('should initialize the file tree', function (ctx) {
|
||||
ctx.ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure.should.have.been.calledWith(
|
||||
ctx.project._id,
|
||||
ctx.docEntries,
|
||||
ctx.fileEntries
|
||||
)
|
||||
})
|
||||
|
||||
it('should notify document updater', function () {
|
||||
this.DocumentUpdaterHandler.promises.updateProjectStructure.should.have.been.calledWith(
|
||||
this.project._id,
|
||||
this.project.overleaf.history.id,
|
||||
this.ownerId,
|
||||
it('should notify document updater', function (ctx) {
|
||||
ctx.DocumentUpdaterHandler.promises.updateProjectStructure.should.have.been.calledWith(
|
||||
ctx.project._id,
|
||||
ctx.project.overleaf.history.id,
|
||||
ctx.ownerId,
|
||||
{
|
||||
newDocs: this.docEntries,
|
||||
newFiles: this.fileEntries,
|
||||
newProject: { version: this.newProjectVersion },
|
||||
newDocs: ctx.docEntries,
|
||||
newFiles: ctx.fileEntries,
|
||||
newProject: { version: ctx.newProjectVersion },
|
||||
},
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
it('should flush the project to TPDS', function () {
|
||||
this.TpdsProjectFlusher.promises.flushProjectToTpds.should.have.been.calledWith(
|
||||
this.project._id
|
||||
it('should flush the project to TPDS', function (ctx) {
|
||||
ctx.TpdsProjectFlusher.promises.flushProjectToTpds.should.have.been.calledWith(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
|
||||
it('should remove the destination directory afterwards', function () {
|
||||
this.fs.promises.rm.should.have.been.calledWith(this.extractedZipPath, {
|
||||
it('should remove the destination directory afterwards', function (ctx) {
|
||||
ctx.fs.promises.rm.should.have.been.calledWith(ctx.extractedZipPath, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
})
|
||||
})
|
||||
|
||||
describe('when initializing the folder structure fails', function () {
|
||||
beforeEach(async function () {
|
||||
this.ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure.rejects()
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure.rejects()
|
||||
await expect(
|
||||
this.ProjectUploadManager.promises.createProjectFromZipArchiveWithName(
|
||||
this.ownerId,
|
||||
this.projectName,
|
||||
this.zipPath
|
||||
ctx.ProjectUploadManager.promises.createProjectFromZipArchiveWithName(
|
||||
ctx.ownerId,
|
||||
ctx.projectName,
|
||||
ctx.zipPath
|
||||
)
|
||||
).to.be.rejected
|
||||
})
|
||||
|
||||
it('should cleanup the blank project created', async function () {
|
||||
this.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
||||
this.project._id
|
||||
it('should cleanup the blank project created', async function (ctx) {
|
||||
ctx.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when setting automatically the root doc fails', function () {
|
||||
beforeEach(async function () {
|
||||
this.ProjectRootDocManager.promises.setRootDocAutomatically.rejects()
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectRootDocManager.promises.setRootDocAutomatically.rejects()
|
||||
await expect(
|
||||
this.ProjectUploadManager.promises.createProjectFromZipArchiveWithName(
|
||||
this.ownerId,
|
||||
this.projectName,
|
||||
this.zipPath
|
||||
ctx.ProjectUploadManager.promises.createProjectFromZipArchiveWithName(
|
||||
ctx.ownerId,
|
||||
ctx.projectName,
|
||||
ctx.zipPath
|
||||
)
|
||||
).to.be.rejected
|
||||
})
|
||||
|
||||
it('should cleanup the blank project created', function () {
|
||||
this.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
||||
this.project._id
|
||||
it('should cleanup the blank project created', function (ctx) {
|
||||
ctx.ProjectDeleter.promises.deleteProject.should.have.been.calledWith(
|
||||
ctx.project._id
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,51 +1,57 @@
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const modulePath = '../../../../app/src/Features/User/UserHandler.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
const modulePath = '../../../../app/src/Features/User/UserHandler.mjs'
|
||||
|
||||
describe('UserHandler', function () {
|
||||
beforeEach(function () {
|
||||
this.user = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.user = {
|
||||
_id: '12390i',
|
||||
email: 'bob@bob.com',
|
||||
remove: sinon.stub().callsArgWith(0),
|
||||
}
|
||||
|
||||
this.TeamInvitesHandler = {
|
||||
ctx.TeamInvitesHandler = {
|
||||
promises: {
|
||||
createTeamInvitesForLegacyInvitedEmail: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.db = {
|
||||
ctx.db = {
|
||||
users: {
|
||||
countDocuments: sinon.stub().resolves(2),
|
||||
},
|
||||
}
|
||||
|
||||
this.UserHandler = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../Subscription/TeamInvitesHandler': this.TeamInvitesHandler,
|
||||
'../../infrastructure/mongodb': { db: this.db },
|
||||
},
|
||||
})
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/TeamInvitesHandler',
|
||||
() => ({
|
||||
default: ctx.TeamInvitesHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/mongodb', () => ({
|
||||
db: ctx.db,
|
||||
READ_PREFERENCE_SECONDARY: 'read-preference-secondary',
|
||||
}))
|
||||
|
||||
ctx.UserHandler = (await import(modulePath)).default
|
||||
})
|
||||
|
||||
describe('populateTeamInvites', function () {
|
||||
beforeEach(async function () {
|
||||
await this.UserHandler.promises.populateTeamInvites(this.user)
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.UserHandler.promises.populateTeamInvites(ctx.user)
|
||||
})
|
||||
|
||||
it('notifies the user about legacy team invites', function () {
|
||||
this.TeamInvitesHandler.promises.createTeamInvitesForLegacyInvitedEmail
|
||||
.calledWith(this.user.email)
|
||||
it('notifies the user about legacy team invites', function (ctx) {
|
||||
ctx.TeamInvitesHandler.promises.createTeamInvitesForLegacyInvitedEmail
|
||||
.calledWith(ctx.user.email)
|
||||
.should.eq(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('countActiveUsers', function () {
|
||||
it('return user count from DB lookup', async function () {
|
||||
expect(await this.UserHandler.promises.countActiveUsers()).to.equal(2)
|
||||
it('return user count from DB lookup', async function (ctx) {
|
||||
expect(await ctx.UserHandler.promises.countActiveUsers()).to.equal(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user